<?
/***************************************************************************
                                    Mdoc.php
                                 --------------
  
       A PHP5 class to build class (or library) documentation from code. 

       $Revision: 1.1 $
       $Date: 2006/02/04 15:23:38 $
       $Source: /cvsroot/phpmanta/phpManta/include/php5/Mdoc.php,v $
       $Author: jmfaure $
       $Name: HEAD $

       /           phpManta <http://phpmanta.sourceforge.net/>
      / \
     /   \         Copyright (c) 2005-2006, JM Faure
   o/     \        <jmfaure at users dot sourceforge dot net>
   |       >-----
   o\     /        Redistribution and use in source and binary forms, 
     \   /         with or without  modification, are permitted provided
      \ /          that the BSD License conditions are met, please
       \           refer to: www.opensource.org/licenses/bsd-license.php

 **************************************************************************/

// Ensure this script is included by a parent file
if (realpath($_SERVER['SCRIPT_FILENAME']) == __FILE__) die("FILE ACESS METHOD NOT ALLOWED");

/**
 * Return flags for getdoc_ and getnav_ methods.
 * 
 * These getdoc_ and getnav_ methods render the documentation in a nice output 
 * format thru a powerful template rendering feature. To help in creating and 
 * maintaining the templates, they can be called with their MDOC_RETURN_TEMPLATE
 * flag set so they will return the template "as is", after eventual customization
 * but without rendering.
 */
define("MDOC_RETURN_NORMAL", 0);    // return normal renderered output 
define("MDOC_RETURN_TEMPLATE", 1);  // return the template array

/**
 * Build class or library documentation from PHP source code.
 *
 * Mdoc is an auto-documentation tool to create manuals like PHP manual at
 * php.net web site, so in a style common to the PHP community and is very 
 * efficient to share your code as APIs. 
 *
 * Mdoc parses the PHP source code and its comments to build the manual 
 * for a class or a library. A class is a valid PHP4 or PHP5 class, a
 * library is file of functions, both may contains constant declarations. 
 * 
 * Documentation may then be displayed formated in hard-coded or custom
 * (X)HTML code which can be included in web pages. Hard-coded format style 
 * is mostly inspired from PHP manual at php.net web site.
 *
 * Custom format is built thanks to a powerful template feature and can
 * be in any structured language such like XML (assuming the template smart 
 * display flag is set to "off" to disable the XHTML smart display feature).
 */
class Mdoc {

/** 
 * Stack to store data elements when fetching template.
 *
 * Data element contains source code documentation which may contain
 * {TEMPLATE_LIKE} strings which could confuse recursive subtsitutions.
 * So this array will store every data element values which are fetch
 * in the last subtsitution pass.
 * 
 * @type array
 */
private $fetch_data = array();

/** 
 * The code source file name.
 *
 * @type string
 */
private $filename;

/** 
 * Stack of parsing error messages.
 *
 * Error messages are appended to the log stack while code source
 * is parsed to build the documentation tree. Most of messages try
 * to help the user in correcting and completing code comments
 * (like a code compiler would do).
 *
 * @type array
 */
private $parser_log = array();

/** 
 * Source code to process (with comments).
 *
 * @type string
*/
private $source;

/** 
 * Source code without comments.
 *
 * @type string
 */
private $stripped_code;

/** 
 * Stack of template handling messages.
 *
 * Template messages are appended to the log while methods try
 * to fill template elements. Most of messages try to help the 
 * user in building efficient templates to output documentation.
 *
 * @type array
 */
private $template_log = array();

/** 
 * The class (or library) documentation in a tree structure.
 *
 * @type array
 */
private $tree = array();

/**
 * Load and store PHP source code to document.
 *
 * When an instance is created, the source code is loaded from the file 
 * in the $source property and the code without comments is stored in the 
 * $stripped_code property.
 *
 * The source file \n chars are converted into server \n to ensure Windows, Mac 
 * and Unix source code compliance.
 *
 * @param (string) $file filename to process (to build the documentation)
 * @return (object) an instance of Mdoc
 */
public function __construct($file) {

  // Load the source related to the input type
  if (!file_exists($file)) {
    trigger_error("file <i>$file</i> doesn't exist", E_USER_ERROR);
    return;
    }
  $this->filename = $file;
  $this->source = file_get_contents($file);

  // Avoid \n issue, converting \n from Windows, Mac or Unix source to server \n
  $this->source = preg_replace('!\r\n|\r|\n!', "\n", $this->source);
  
  // Save also code without comments
  $this->stripped_code = $this->uparse_removecomments($this->source);
}

/*****************************************************************************/
/*                                                                           */
/*                     HTML output formating methods                         */
/*                                                                           */
/*****************************************************************************/

/**
 * Return the documentation of the current class.
 *
 * This method builds the documentation page for the class found in the current
 * source code file. A native XHTML template is hard-coded to render the page
 * in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => class must be coded in PHP
 *    'ERRMSG:not_a_class' => method called on a file which is not a class
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the class "formated" documentation page
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getdoc_class($view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP class
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  $type = empty($this->tree['main']['type']) ?
          "unknown" :
          $this->tree['main']['type'];
  if (!in_array($type, array("class", "abstract_class", "final_class", "interface"))) {
    $err = empty($template['ERRMSG:not_a_class']) ?
           "Current file isn't coding a class (type is $type)." :
           $template['ERRMSG:not_a_class'];
    return $err;
  }
  
  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<h1>{TREE.main.name}</h1>\n" .
          "<p>Class {TREE.main.name} -- {TREE.main.description.short}</p>\n" .
          "{TREE.main.source.extends?not_blank}" .
          "{TREE.main.source.implements?not_blank}" .
          "{TREE.main.info?not_blank}" .
          "{TREE.main.info.warning?not_blank}" .
          "{TREE.main.description.long?not_blank}" .
          "{TREE.main.info.experimental?not_blank}" .
          "<h2>Methods</h2>\n" .
          "{TREE.method?not_blank}" .
          "{TREE.property?not_blank}" .
          "{TREE.main.constants?not_empty}" .
          "{TREE.cvs?not_blank}";
  // Public view
  $tpl['VIEW:public'] =           
          "<h1>{TREE.main.name}</h1>\n" .
          "<p>Class {TREE.main.name} -- {TREE.main.description.short}</p>\n" .
          "{TREE.main.source.extends?not_blank}" .
          "{TREE.main.source.implements?not_blank}" .
          "{TREE.main.info.warning?not_blank}" .
          "{TREE.main.description.long?not_blank}" .
          "{TREE.main.info.experimental?not_blank}" .
          "<h2>Methods</h2>\n" .
          "{TREE.public_method#public?not_blank}" .
          "{TREE.public_property#public?not_blank}" .
          "{TREE.main.constants?not_empty}";
  // Long description if not blank (?blank not required, default is "")
  $tpl['TREE.main.description.long?not_blank'] =
          "<h2>Description</h2>\n" .
          "<p>{TREE.main.description.long}</p>\n";
  // Inheritance
  $tpl['TREE.main.source.extends?not_blank'] = "<p>This class inherits from {EXTENDS_URL} class.</p>\n";
  $tpl['EXTENDS_URL'] = 
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.source.extends}" .
          "&view={VIEW}\" class=\"classurl\">{TREE.main.source.extends}</a>";
  // Interfaces
  $tpl['TREE.main.source.implements?not_blank'] = "<p>This class implements interfaces: {TREE.main.source.implements}.</p>\n";
  $tpl['TREE.main.source.implements:frame'] = "{TREE.main.source.implements:any}";
  $tpl['TREE.main.source.implements:first'] =
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?class={IMPLEMENTS%Value}" .
          "&view={VIEW}\" class=\"classurl\">{IMPLEMENTS%Value}</a>";
  $tpl['TREE.main.source.implements:any'] =
          ", <a href=\"" . $_SERVER['PHP_SELF'] . "?class={IMPLEMENTS%Value}" .
          "&view={VIEW}\" class=\"classurl\">{IMPLEMENTS%Value}</a>";
  // Optional CVS keywords (revision log excluded)
  $tpl['TREE.cvs?not_blank'] = "{TREE.cvs}";
  $tpl['TREE.cvs:frame'] = 
          "<h2>CVS</h2>\n" .
          "<p>This source code file has some CVS keywords expanded:</p>\n" .
          "<table class=\"cvsinfo\">\n{TREE.cvs:any}</table>\n";
  $tpl['TREE.cvs:any'] = "<tr><td>{CVS%Keyword}: </td><td> {CVS%Value}</td></tr>\n";
  $tpl['TREE.cvs:exclude'] = array('Log');
  // Main infos
  $tpl['TREE.main.info?not_blank'] = "{TREE.main.info}";
  $tpl['TREE.main.info:frame'] = 
          "<table class=\"classinfo\">\n" .
          "<tr><td colspan=\"2\">Author informations</td></tr>\n" .
          "{TREE.main.info:any}" .
          "</table>\n";
  $tpl['TREE.main.info:any'] = "<tr><td>@{INFO%Keyword}&nbsp; </td><td> {INFO%Value}</td></tr>\n";
  $tpl['TREE.main.info:exclude'] = array("experimental", "warning");
  // Warning
  $tpl['TREE.main.info.warning?not_blank'] = "<p><b>Warning: {TREE.main.info.warning}</b></p>\n";
  // Experimental status
  $tpl['TREE.main.info.experimental?not_blank'] = 
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>This class is <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of this class, the name of this class, and anything\n" .
          "else documented about this class may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";
  // Methods
  $tpl['TREE.method?not_blank'] = 
          "{TREE.public_method?not_blank}" .
          "{TREE.private_method?not_blank}" .
          "{TREE.protected_method?not_blank}";
  $tpl['TREE.method?blank'] = "<p>This class has no methods defined.</p>\n";
  // Public methods
  $tpl['TREE.public_method?not_blank'] = 
          "<h3>Public methods</h3>\n" .
          "<dl>\n" .
          "{TREE.public_method}" .
          "</dl>\n";
  $tpl['TREE.public_method?blank'] = 
          "<h3>Public methods</h3>\n" .
          "<p>This class has no public methods defined.</p>\n";
  $tpl['TREE.public_method:frame'] = "{TREE.public_method:any}";
  $tpl['TREE.public_method:any'] = 
          "  <dt>{PUBLIC_METHOD_URL} -- {PUBLIC_METHOD.description.short}</dt>\n";
  $tpl['PUBLIC_METHOD_URL'] = 
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&method={PUBLIC_METHOD}" .
          "&view={VIEW}\" class=\"methodurl\">{PUBLIC_METHOD}</a>";
  // -- for "public" view
  $tpl['TREE.public_method#public?not_blank'] = 
          "<dl>\n" .
          "{TREE.public_method}" .
          "</dl>\n";
  $tpl['TREE.public_method#public?blank'] = 
          "<p>This class has no public methods defined.</p>\n";
  // Private methods
  $tpl['TREE.private_method?not_blank'] = 
          "<h3>Private methods</h3>\n" .
          "<dl>\n" .
          "{TREE.private_method}" .
          "</dl>\n";
  $tpl['TREE.private_method?blank'] = 
          "<h3>Private methods</h3>\n" .
          "<p>This class has no private methods defined.</p>\n";
  $tpl['TREE.private_method:frame'] = "{TREE.private_method:any}";
  $tpl['TREE.private_method:any'] = 
          "  <dt>{PRIVATE_METHOD_URL} -- {PRIVATE_METHOD.description.short}</dt>\n";
  $tpl['PRIVATE_METHOD_URL'] = 
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&method={PRIVATE_METHOD}" .
          "&view={VIEW}\" class=\"methodurl\">{PRIVATE_METHOD}</a>";
  // Protected methods
  $tpl['TREE.protected_method?not_blank'] = 
          "<h3>Protected methods</h3>\n" .
          "<dl>\n" .
          "{TREE.protected_method}" .
          "</dl>\n";
  $tpl['TREE.protected_method?blank'] = 
          "<h3>Protected methods</h3>\n" .
          "<p>This class has no protected methods defined.</p>\n";
  $tpl['TREE.protected_method:frame'] = "{TREE.protected_method:any}";
  $tpl['TREE.protected_method:any'] = 
          "  <dt>{PROTECTED_METHOD_URL} -- {PROTECTED_METHOD.description.short}</dt>\n";
  $tpl['PROTECTED_METHOD_URL'] = 
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&method={PROTECTED_METHOD}" .
          "&view={VIEW}\" class=\"methodurl\">{PROTECTED_METHOD}</a>";
  // Properties
  $tpl['TREE.property?not_blank'] = 
          "{TREE.public_property?not_blank}" .
          "{TREE.private_property?not_blank}" .
          "{TREE.protected_property?not_blank}";
  // Public properties
  $tpl['TREE.public_property?not_blank'] = 
          "<h3>Public properties</h3>\n" .
          "<dl>\n" .
          "{TREE.public_property}" .
          "</dl>\n";
  $tpl['TREE.public_property?blank'] = 
          "<h3>Public properties</h3>\n" .
          "<p>This class has no public properties defined.</p>\n";
  $tpl['TREE.public_property:frame'] = "{TREE.public_property:any}";
  $tpl['TREE.public_property:any'] = 
          "  <dt>{PUBLIC_PROPERTY_URL} -- {PUBLIC_PROPERTY.description.short}</dt>\n";
  $tpl['PUBLIC_PROPERTY_URL'] = 
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&property={PUBLIC_PROPERTY}" .
          "&view={VIEW}\" class=\"propertyurl\">{PUBLIC_PROPERTY}</a>";
  // -- for "public" view
  $tpl['TREE.public_property#public?not_blank'] = 
          "<h2>Properties</h2>\n" .
          "<dl>\n" .
          "{TREE.public_property}" .
          "</dl>\n";
  // Private properties
  $tpl['TREE.private_property?not_blank'] = 
          "<h3>Private properties</h3>\n" .
          "<dl>\n" .
          "{TREE.private_property}" .
          "</dl>\n";
  $tpl['TREE.private_property?blank'] = 
          "<h3>Private properties</h3>\n" .
          "<p>This class has no private properties defined.</p>\n";
  $tpl['TREE.private_property:frame'] = "{TREE.private_property:any}";
  $tpl['TREE.private_property:any'] = 
          "  <dt>{PRIVATE_PROPERTY_URL} -- {PRIVATE_PROPERTY.description.short}</dt>\n";
  $tpl['PRIVATE_PROPERTY_URL'] = 
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&property={PRIVATE_PROPERTY}" .
          "&view={VIEW}\" class=\"propertyurl\">{PRIVATE_PROPERTY}</a>";
  // Protected properties
  $tpl['TREE.protected_property?not_blank'] = 
          "<h3>Protected properties</h3>\n" .
          "<dl>\n" .
          "{TREE.protected_property}" .
          "</dl>\n";
  $tpl['TREE.protected_property?blank'] = 
          "<h3>Protected properties</h3>\n" .
          "<p>This class has no protected properties defined.</p>\n";
  $tpl['TREE.protected_property:frame'] = "{TREE.protected_property:any}";
  $tpl['TREE.protected_property:any'] = 
          "  <dt>{PROTECTED_PROPERTY_URL} -- {PROTECTED_PROPERTY.description.short}</dt>\n";
  $tpl['PROTECTED_PROPERTY_URL'] = 
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&property={PROTECTED_PROPERTY}" .
          "&view={VIEW}\" class=\"propertyurl\">{PROTECTED_PROPERTY}</a>";
  // Constants and constant groups
  $tpl['TREE.main.constants?not_empty'] =
          "<h2>Constants</h2>\n" .
          "<dl>\n" .
          "{TREE.constant?not_blank}" .
          "{TREE.constant_group?not_blank}" .
          "</dl>\n" .
          "{CONSTANTS_URL}";
  // Constants
  $tpl['TREE.constant?not_blank'] = "{TREE.constant}";
  $tpl['TREE.constant:frame'] = "{TREE.constant:any}";
  $tpl['TREE.constant:any'] = "  <dt><b class=\"constant\">{CONSTANT}</b> -- {CONSTANT.description.short}</dt>\n";
  // Constant groups
  $tpl['TREE.constant_group?not_blank'] = "{TREE.constant_group}";
  $tpl['TREE.constant_group:frame'] = "{TREE.constant_group:any}";
  $tpl['TREE.constant_group:any'] = 
          "  <dt><p>{CONSTANT_GROUP.description.short}</p>\n" .
          "      <dl>\n" .
          "{CONSTANT_GROUP.constant}" .
          "      </dl>\n" .
          "  </dt>\n";
  $tpl['CONSTANT_GROUP.constant:frame'] = "{CONSTANT_GROUP.constant:any}";
  $tpl['CONSTANT_GROUP.constant:any'] = "        <dt><b class=\"constant\">{CONSTANT}</b> -- {CONSTANT.comment}</dt>\n";
 // Constants URL
  $tpl['CONSTANTS_URL'] = 
          "<p><a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&constants" .
          "&view={VIEW}\" class=\"constanturl\">View constants documentation.</a></p>";

  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $scope = "TREE";
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the documentation of the current library.
 *
 * This method builds the documentation page for the library found in the current
 * source code file. A native XHTML template is hard-coded to render the page
 * in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => library must be coded in PHP
 *    'ERRMSG:not_a_library' => method called on a file which is not a library
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the library "formated" documentation page
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getdoc_library($view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP library
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  $type = empty($this->tree['main']['type']) ?
          "unknown" :
          $this->tree['main']['type'];
  if ($type != "library") {
    $err = empty($template['ERRMSG:not_a_library']) ?
           "Current file isn't coding a library (type is $type)." :
           $template['ERRMSG:not_a_library'];
    return $err;
  }
  
  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<h1>{TREE.main.name}</h1>\n" .
          "<p>Library {TREE.main.name} -- {TREE.main.description.short}</p>\n" .
          "{TREE.main.info?not_blank}" .
          "{TREE.main.info.warning?not_blank}" .
          "{TREE.main.description.long?not_blank}" .
          "{TREE.main.info.experimental?not_blank}" .
          "<h2>Functions</h2>\n" .
          "{TREE.function?not_blank}" .
          "{TREE.main.constants?not_empty}" .
          "{TREE.cvs?not_blank}";
  // Public view
  $tpl['VIEW:public'] = 
          "<h1>{TREE.main.name}</h1>\n" .
          "<p>Library {TREE.main.name} -- {TREE.main.description.short}</p>\n" .
          "{TREE.main.info.warning?not_blank}" .
          "{TREE.main.description.long?not_blank}" .
          "{TREE.main.info.experimental?not_blank}" .
          "<h2>Functions</h2>\n" .
          "{TREE.function?not_blank}" .
          "{TREE.main.constants?not_empty}";
  // Long description if not blank (?blank not required, default is "")
  $tpl['TREE.main.description.long?not_blank'] =
          "<h2>Description</h2>\n" .
          "<p>{TREE.main.description.long}</p>\n";
  // Optional CVS keywords (revision log excluded)
  $tpl['TREE.cvs?not_blank'] = "{TREE.cvs}";
  $tpl['TREE.cvs:frame'] = 
          "<h2>CVS</h2>\n" .
          "<p>This source code file has some CVS keywords expanded:</p>\n" .
          "<table class=\"cvsinfo\">\n{TREE.cvs:any}</table>\n";
  $tpl['TREE.cvs:any'] = "<tr><td>{CVS%Keyword}: </td><td> {CVS%Value}</td></tr>\n";
  $tpl['TREE.cvs:exclude'] = array('Log');
  // Main infos
  $tpl['TREE.main.info?not_blank'] = "{TREE.main.info}";
  $tpl['TREE.main.info:frame'] = 
          "<table class=\"libinfo\">\n" .
          "<tr><td colspan=\"2\">Author informations</td></tr>\n" .
          "{TREE.main.info:any}" .
          "</table>\n";
  $tpl['TREE.main.info:any'] = "<tr><td>@{INFO%Keyword}&nbsp; </td><td> {INFO%Value}</td></tr>\n";
  $tpl['TREE.main.info:exclude'] = array("experimental", "warning");
  // Warning
  $tpl['TREE.main.info.warning?not_blank'] = "<p><b>Warning: {TREE.main.info.warning}</b></p>\n";
  // Experimental status
  $tpl['TREE.main.info.experimental?not_blank'] = 
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>This library is <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of this library, the name of this library, and anything\n" .
          "else documented about this library may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";
  // Functions
  $tpl['TREE.function?not_blank'] = "{TREE.function}";
  $tpl['TREE.function?blank'] = "<p>This library has no functions defined.</p>\n";
  $tpl['TREE.function:frame'] = 
          "<dl>\n" .
          "{TREE.function:any}" .
          "</dl>\n";
  $tpl['TREE.function:any'] = "  <dt>{FUNCTION_URL} -- {FUNCTION.description.short}</dt>\n";
  $tpl['FUNCTION_URL'] = 
          "<a href=\"" . $_SERVER['PHP_SELF'] . "?lib={TREE.main.info.lib}&function={FUNCTION}" .
          "&view={VIEW}\" class=\"functionurl\">{FUNCTION}</a>";
  // Constants and constant groups
  $tpl['TREE.main.constants?not_empty'] =
          "<h2>Constants</h2>\n" .
          "<dl>\n" .
          "{TREE.constant?not_blank}" .
          "{TREE.constant_group?not_blank}" .
          "</dl>\n" .
          "{CONSTANTS_URL}";
  // Constants
  $tpl['TREE.constant?not_blank'] = "{TREE.constant}";
  $tpl['TREE.constant:frame'] = "{TREE.constant:any}";
  $tpl['TREE.constant:any'] = "  <dt><b class=\"constant\">{CONSTANT}</b> -- {CONSTANT.description.short}</dt>\n";
  // Constant groups
  $tpl['TREE.constant_group?not_blank'] = "{TREE.constant_group}";
  $tpl['TREE.constant_group:frame'] = "{TREE.constant_group:any}";
  $tpl['TREE.constant_group:any'] = 
          "  <dt><p>{CONSTANT_GROUP.description.short}</p>\n" .
          "      <dl>\n" .
          "{CONSTANT_GROUP.constant}" .
          "      </dl>\n" .
          "  </dt>\n";
  $tpl['CONSTANT_GROUP.constant:frame'] = "{CONSTANT_GROUP.constant:any}";
  $tpl['CONSTANT_GROUP.constant:any'] = "        <dt><b class=\"constant\">{CONSTANT}</b> -- {CONSTANT.comment}</dt>\n";
 // Constants URL
  $tpl['CONSTANTS_URL'] = 
          "<p><a href=\"" . $_SERVER['PHP_SELF'] . "?lib={TREE.main.info.lib}&constants" .
          "&view={VIEW}\" class=\"constanturl\">View constants documentation.</a></p>";

  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $scope = "TREE";
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the documentation of all constants.
 *
 * This method builds the documentation page for all constants declared in the 
 * current source code file. A native XHTML template is hard-coded to render 
 * the page in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => class or library must be coded in PHP
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the constants "formated" documentation page
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getdoc_constants($view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP code with at least one constant
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  
  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<h1>Constants of {TREE.main.name}</h1>\n" .
          "{TREE.main.constants?not_empty}" .
          "{TREE.main.constants#values?not_empty}";
  // Public view
  $tpl['VIEW:public'] =
          "<h1>Constants of {TREE.main.name}</h1>\n" .
          "{TREE.main.constants?not_empty}";
  // Constants and constant groups
  $tpl['TREE.main.constants?empty'] = "<p>This source file has no constants defined.</p>\n";
  $tpl['TREE.main.constants?not_empty'] =
          "<h2>Constants</h2>\n" .
          "{TREE.constant?not_blank}" .
          "{TREE.constant_group?not_blank}";
  // Constants declared single
  $tpl['TREE.constant?not_blank'] = "{TREE.constant}";
  $tpl['TREE.constant:frame'] = "{TREE.constant:any}";
  $tpl['TREE.constant:any'] = 
          "<p>{CONSTANT.description.short}</p>\n" .
          "<blockquote>\n" .
          "{CONSTANT.info.warning?not_blank}" .
          "{CONSTANT.info.experimental?not_blank}" .
          "<p><b class=\"constant\">{CONSTANT}</b>{CONSTANT.info.type?not_blank}</p>\n" .
          "{CONSTANT.description.long?not_blank}" .
          "<p><a href=\"" . $_SERVER['PHP_SELF'] . "?{TREE.main.type?=library}&constant={CONSTANT}" .
          "&view={VIEW}\" class=\"constanturl\">Detailed documentation.</a></p>" .
          "</blockquote>\n";
  // Lib or class URL
  $tpl['TREE.main.type?=library'] = "lib={TREE.main.name}";
  $tpl['TREE.main.type?!=library'] = "class={TREE.main.name}";
  // Elements in CONSTANT scope
  $tpl['CONSTANT.description.long?not_blank'] = "<p>{CONSTANT.description.long}</p>\n";
  $tpl['CONSTANT.info.warning?not_blank'] = "<p><b>Warning: {CONSTANT.info.warning}</b></p>\n";
  $tpl['CONSTANT.info.experimental?not_blank'] =
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>This constant is <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of this constant, the name of this constant, and anything\n" .
          "else documented about this constant may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";
  $tpl['CONSTANT.info.type?not_blank'] = ' ({CONSTANT.info.type})';
  // Constants declared in groups
  $tpl['TREE.constant_group?not_blank'] = "{TREE.constant_group}";
  $tpl['TREE.constant_group:frame'] =
          "<dl>\n" .
          "{TREE.constant_group:any}" .
          "</dl>\n";
  $tpl['TREE.constant_group:any'] =
          "<p>{CONSTANT_GROUP.description.short}</p>\n" .
          "<blockquote>\n" .
          "{CONSTANT_GROUP.info.warning?not_blank}" .
          "{CONSTANT_GROUP.info.experimental?not_blank}" .
          "<p>\n" .
          "{CONSTANT_GROUP.constant}" .
          "</p>\n" .
          "{CONSTANT_GROUP.description.long?not_blank}" .
          "<p><a href=\"" . $_SERVER['PHP_SELF'] . "?{TREE.main.type?=library}" .
          "&constant_group={CONSTANT_GROUP}&view={VIEW}\" class=\"constanturl\">Detailed documentation.</a></p>" .
          "</blockquote>\n";
  // Elements in CONSTANT_GROUP scope
  $tpl['CONSTANT_GROUP.description.long?not_blank'] = "<p>{CONSTANT_GROUP.description.long}</p>\n";
  $tpl['CONSTANT_GROUP.info.warning?not_blank'] = "<p><b>Warning: {CONSTANT_GROUP.info.warning}</b></p>\n";
  $tpl['CONSTANT_GROUP.info.experimental?not_blank'] =
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>This group of constant is <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of these constants, the name of these constants, and anything\n" .
          "else documented about these constants may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";
  // Constant list in constant groups (scope CONSTANT_GROUP.constant a.k.a. CONSTANT)
  $tpl['CONSTANT_GROUP.constant:frame'] = "{CONSTANT_GROUP.constant:any}";
  $tpl['CONSTANT_GROUP.constant:any'] = "<b class=\"constant\">{CONSTANT}</b>{CONSTANT.comment?not_blank}<br />\n";
  $tpl['CONSTANT.comment?not_blank'] = " -- {CONSTANT.comment}";
  // Constant values
  $tpl['TREE.main.constants#values?not_empty'] =
          "<h2>Constant values</h2>\n" .
          "<table border=\"0\" cellspacing=\"0\" cellpadding=\"2\">\n" .
          "{TREE.constant#values?not_empty}" .
          "{TREE.constant_group#values?not_empty}" .
          "</table>\n";
  $tpl['TREE.constant#values?not_empty'] = "{TREE.constant#values}";
  $tpl['TREE.constant_group#values?not_empty'] = "{TREE.constant_group#values}";
  $tpl['TREE.constant#values:frame'] = "{TREE.constant#values:any}";
  $tpl['TREE.constant#values:any'] =
          "<tr>\n" .
          "  <td valign=\"top\"><b class=\"constant\">{CONSTANT}</b></td>\n" .
          "  <td valign=\"top\"> = </td>\n" .
          "  <td style=\"padding: 2px 0px 8px 8px\"><code class=\"value\">{CONSTANT.source.value}</code></td>\n" .
          "</tr>\n";
  $tpl['TREE.constant_group#values:frame'] = "{TREE.constant_group#values:any}";
  $tpl['TREE.constant_group#values:any'] = "{CONSTANT_GROUP.constant#values}";
  $tpl['CONSTANT_GROUP.constant#values:frame'] = "{CONSTANT_GROUP.constant#values:any}";
  $tpl['CONSTANT_GROUP.constant#values:any'] =
          "<tr>\n" .
          "  <td valign=\"top\"><b class=\"constant\">{CONSTANT}</b></td>\n" .
          "  <td valign=\"top\"> = </td>\n" .
          "  <td style=\"padding: 2px 0px 8px 8px\"><code class=\"value\">{CONSTANT.value}</code></td>\n" .
          "</tr>\n";

  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $scope = "TREE";
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the documentation of the specified constant.
 *
 * This method builds the documentation page for the specified constant declared 
 * in the current source code file. A native XHTML template is hard-coded to render 
 * the page in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => class or library must be coded in PHP
 *    'ERRMSG:constant_not_found' => constant not found in the current class or library
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $constant the constant identifier
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the constants "formated" documentation page
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getdoc_constant($constant, $view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP code
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  
  // Find the constant data in singles
  if (array_key_exists($constant, $this->tree['constant'])) {
    $scope_data = $this->tree['constant'][$constant];
  } else {
    $err = empty($template['ERRMSG:constant_not_found']) ?
           "Constant $constant not found in this file." :
           $template['ERRMSG:constant_not_found'];
    return $err;
  }
  
  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<h1>{CONSTANT}</h1>\n" .
          "<p>Constant {CONSTANT}{CONSTANT.info.type?not_blank} -- {CONSTANT.description.short}</p>\n" .
          "{CONSTANT.info?not_blank}" .
          "{CONSTANT.info.warning?not_blank}" .
          "{CONSTANT.description.long?not_blank}" .
          "{CONSTANT.info.experimental?not_blank}" .
          "<h2>Value</h2>\n" .
          "<p><code class=\"value\">{CONSTANT.source.value}</code></p>\n" .
          "<h2>Source code</h2>\n" .
          "<p>The constant is implemented at line {CONSTANT.source.line} in source code.</p>\n" .
          "<table border=\"0\" cellpadding=\"5\" bgcolor=\"#E0E0E0\"><tr><td>\n" .
          "{CONSTANT.source.code}" .
          "\n</td></tr></table>\n" .
          "{CONSTANTS_URL}";
  $tpl['VIEW:public'] =
          "<h1>{CONSTANT}</h1>\n" .
          "<p>Constant {CONSTANT}{CONSTANT.info.type?not_blank} -- {CONSTANT.description.short}</p>\n" .
          "{CONSTANT.info.warning?not_blank}" .
          "{CONSTANT.description.long?not_blank}" .
          "{CONSTANT.info.experimental?not_blank}" .
          "<h2>Value</h2>\n" .
          "<p><code class=\"value\">{CONSTANT.source.value}</code></p>\n" .
          "{CONSTANTS_URL}";
  // Infos
  $tpl['CONSTANT.info?not_blank'] = "{CONSTANT.info}";
  $tpl['CONSTANT.info:frame'] = 
          "<table class=\"constantinfo\">\n" .
          "<tr><td colspan=\"2\">Author informations</td></tr>\n" .
          "{CONSTANT.info:any}" .
          "</table>\n";
  $tpl['CONSTANT.info:any'] = "<tr><td>@{INFO%Keyword}&nbsp; </td><td> {INFO%Value}</td></tr>\n";
  $tpl['CONSTANT.info:exclude'] = array("experimental", "type", "warning"); 
  // Type
  $tpl['CONSTANT.info.type?not_blank'] = ' ({CONSTANT.info.type})';
  // Description
  $tpl['CONSTANT.description.long?not_blank'] = 
          "<h2>Description</h2>\n" .
          "<p>{CONSTANT.description.long}</p>\n";
  // Warning
  $tpl['CONSTANT.info.warning?not_blank'] = "<p><b>Warning: {CONSTANT.info.warning}</b></p>\n";
  // Experimental status
  $tpl['CONSTANT.info.experimental?not_blank'] =
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>This constant is <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of this constant, the name of this constant, and anything\n" .
          "else documented about this constant may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";
  // Constants URL
  $tpl['CONSTANTS_URL'] = "<p><a href=\"" . $_SERVER['PHP_SELF'] . "?{TREE.main.type?=library}" .
                          "&constants&view={VIEW}\" class=\"constanturl\">View constants documentation.</a></p>";
  $tpl['TREE.main.type?=library'] = "lib={TREE.main.name}";
  $tpl['TREE.main.type?!=library'] = "class={TREE.main.name}";

  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level and scope data
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $data["CONSTANT"] = $scope_data;
    $scope = "CONSTANT";
    $tpl['CONSTANT'] = $constant;
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the documentation of the specified constant group.
 *
 * This method builds the documentation page for the specified constant group declared 
 * in the current source code file. A native XHTML template is hard-coded to render 
 * the page in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => class or library must be coded in PHP
 *    'ERRMSG:constant_group_not_found' => constant group not found in the 
 *                                         current class or library
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $group_index the constant group index in the documentation tree, 
 *                              starting from 1
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the constant group "formated" documentation page
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getdoc_constant_group($group_index, $view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP code
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  
  // Found the group in constant groups
  if (array_key_exists($group_index, $this->tree['constant_group'])) {
    $scope_data = $this->tree['constant_group'][$group_index];
  } else {
    $err = empty($template['ERRMSG:constant_group_not_found']) ?
           "Constant group (index $group_index) not found in this file." :
           $template['ERRMSG:constant_group_not_found'];
    return $err;
  }
  
  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<h1>Constant group ({CONSTANT_GROUP}) of {TREE.main.name}</h1>\n" .
          "<p>{CONSTANT_GROUP.description.short}</p>\n" .
          "{CONSTANT_GROUP.constant}" .
          "{CONSTANT_GROUP.info?not_blank}" .
          "{CONSTANT_GROUP.info.warning?not_blank}" .
          "{CONSTANT_GROUP.description.long?not_blank}" .
          "{CONSTANT_GROUP.info.experimental?not_blank}" . 
          "<h2>Values</h2>\n" .
          "{CONSTANT_GROUP.constant#values}" .
          "<h2>Source code</h2>\n" .
          "<p>The constants are implemented at line {CONSTANT_GROUP.source.line} in source code.</p>\n" .
          "<table border=\"0\" cellpadding=\"5\" bgcolor=\"#E0E0E0\"><tr><td>\n" .
          "{CONSTANT_GROUP.source.code}" .
          "\n</td></tr></table>\n" .
          "{CONSTANTS_URL}";
  $tpl['VIEW:public'] =
          "<h1>Constant group ({CONSTANT_GROUP}) of {TREE.main.name}</h1>\n" .
          "<p>{CONSTANT_GROUP.description.short}</p>\n" .
          "{CONSTANT_GROUP.constant}" .
          "{CONSTANT_GROUP.info.warning?not_blank}" .
          "{CONSTANT_GROUP.description.long?not_blank}" .
          "{CONSTANT_GROUP.info.experimental?not_blank}" . 
          "<h2>Values</h2>\n" .
          "{CONSTANT_GROUP.constant#values}" .
          "{CONSTANTS_URL}";
  
  // Description
  $tpl['CONSTANT_GROUP.description.long?not_blank'] = 
          "<h2>Description</h2>\n" .
          "<p>{CONSTANT_GROUP.description.long}</p>\n";
  // Warning
  $tpl['CONSTANT_GROUP.info.warning?not_blank'] = "<p><b>Warning: {CONSTANT_GROUP.info.warning}</b></p>\n";
  // Experimental status
  $tpl['CONSTANT_GROUP.info.experimental?not_blank'] =
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>These constants are <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of these constants, the names of these constants, and anything\n" .
          "else documented about these constants may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";
  // Main infos
  $tpl['CONSTANT_GROUP.info?not_blank'] = "{CONSTANT_GROUP.info}";
  $tpl['CONSTANT_GROUP.info:frame'] = 
          "<table class=\"constantinfo\">\n" .
          "<tr><td colspan=\"2\">Author informations</td></tr>\n" .
          "{CONSTANT_GROUP.info:any}" .
          "</table>\n";
  $tpl['CONSTANT_GROUP.info:any'] = "<tr><td>{INFO%Keyword}: </td><td> {INFO%Value}</td></tr>\n";
  $tpl['CONSTANT_GROUP.info:exclude'] = array('experimental', 'warning');
  // Constants and comments
  $tpl['CONSTANT_GROUP.constant:frame'] = "<dl>\n{CONSTANT_GROUP.constant:any}</dl>\n"; 
  $tpl['CONSTANT_GROUP.constant:any'] = "  <dt><b class=\"constant\">{CONSTANT}</b>{CONSTANT.comment?not_blank}</dt>\n";
  $tpl['CONSTANT.comment?not_blank'] = " -- {CONSTANT.comment}";
  // Constant values
  $tpl['CONSTANT_GROUP.constant#values:frame'] = 
          "<table border=\"0\" cellspacing=\"0\" cellpadding=\"2\">\n" .
          "{CONSTANT_GROUP.constant#values:any}" .
          "</table>\n";
  $tpl['CONSTANT_GROUP.constant#values:any'] = 
          "<tr>\n" .
          "  <td valign=\"top\"><b class=\"constant\">{CONSTANT}</b></td>\n" .
          "  <td valign=\"top\"> = </td>\n" .
          "  <td style=\"padding: 2px 0px 8px 8px\"><code class=\"value\">{CONSTANT.value}</code></td>\n" .
          "</tr>\n";
  // Constants URL
  $tpl['CONSTANTS_URL'] = "<p><a href=\"" . $_SERVER['PHP_SELF'] . "?{TREE.main.type?=library}" .
                          "&constants&view={VIEW}\" class=\"constanturl\">View constants documentation.</a></p>";
  $tpl['TREE.main.type?=library'] = "lib={TREE.main.name}";
  $tpl['TREE.main.type?!=library'] = "class={TREE.main.name}";

  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level and scope data
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $data["CONSTANT_GROUP"] = $scope_data;
    $scope = "CONSTANT_GROUP";
    $tpl['CONSTANT_GROUP'] = $group_index;
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the documentation of the specified function.
 *
 * This method builds the documentation page for the specified function declared 
 * in the current source code file. A native XHTML template is hard-coded to render 
 * the page in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => library must be coded in PHP
 *    'ERRMSG:not_a_library' => method called on a file which is not a library
 *    'ERRMSG:function_not_found' => function not found in the current library 
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $function the function identifier
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the function "formated" documentation page
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getdoc_function($function, $view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP library
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  $type = empty($this->tree['main']['type']) ?
          "unknown" :
          $this->tree['main']['type'];
  if ($type != "library") {
    $err = empty($template['ERRMSG:not_a_library']) ?
           "Current file isn't coding a library (type is $type)." :
           $template['ERRMSG:not_a_library'];
    return $err;
  }

  // Find the function
  if (array_key_exists($function, $this->tree['function'])) {
    $scope_data = $this->tree['function'][$function];
  } else {
    $err = empty($template['ERRMSG:function_not_found']) ?
           "Function $function not found in this file." :
           $template['ERRMSG:function_not_found'];
    return $err;
  }

  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<h1>{FUNCTION}</h1>\n" .
          "<p>Function {FUNCTION} -- {FUNCTION.description.short}</p>\n" .
          "{FUNCTION.info?not_blank}" .
          "{FUNCTION.info.warning?not_blank}" .
          "<h2>Description</h2>\n" .
          "<p>{FUNCTION_SYNTAX}</p>\n" .
          "{FUNCTION.description.long?not_blank}" .
          "{FUNCTION.info.experimental?not_blank}" .
          "<h2>Parameters</h2>\n" .
          "{FUNCTION.param?not_blank}" .
          "<h2>Return values</h2>\n" .
          "{FUNCTION.return?not_blank}" .
          "<h2>Source code</h2>\n" .
          "<p>The function is implemented at line {FUNCTION.source.line} in source code.</p>\n" .
          "<table border=\"0\" cellpadding=\"5\" bgcolor=\"#E0E0E0\"><tr><td>\n" .
          "{FUNCTION.source.code}" .
          "\n</td></tr></table>\n";
  $tpl['VIEW:public'] =
          "<h1>{FUNCTION}</h1>\n" .
          "<p>Function {FUNCTION} -- {FUNCTION.description.short}</p>\n" .
          "{FUNCTION.info.warning?not_blank}" .
          "<h2>Description</h2>\n" .
          "<p>{FUNCTION.SYNTAX}</p>\n" .
          "{FUNCTION.description.long?not_blank}" .
          "{FUNCTION.info.experimental?not_blank}" .
          "<h2>Parameters</h2>\n" .
          "{FUNCTION.param?not_blank}" .
          "<h2>Return values</h2>\n" .
          "{FUNCTION.return?not_blank}";
  // Syntax string
  $tpl['FUNCTION_SYNTAX'] = "{FUNCTION.source.return_type} <b class=\"functionname\">{FUNCTION}</b> ( {FUNCTION.source.parameters} )";
  // Infos
  $tpl['FUNCTION.info?not_blank'] = "{FUNCTION.info}";
  $tpl['FUNCTION.info:frame'] = 
          "<table class=\"functioninfo\">\n" .
          "<tr><td colspan=\"2\">Author informations</td></tr>\n" .
          "{FUNCTION.info:any}" .
          "</table>\n";
  $tpl['FUNCTION.info:any'] = "<tr><td>@{INFO%Keyword}&nbsp; </td><td> {INFO%Value}</td></tr>\n";
  $tpl['FUNCTION.info:exclude'] = array('experimental', 'warning');
  // Description
  $tpl['FUNCTION.description.long?not_blank'] = 
          "<p>{FUNCTION.description.long}</p>\n";
  // Warning
  $tpl['FUNCTION.info.warning?not_blank'] = "<p><b>Warning: {FUNCTION.info.warning}</b></p>\n";
  // Experimental status
  $tpl['FUNCTION.info.experimental?not_blank'] =
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>This function is <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of this function, the name of this function, and anything\n" .
          "else documented about this function may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";  
  // Parameters
  $tpl['FUNCTION.param?not_blank'] = "{FUNCTION.param}";
  $tpl['FUNCTION.param?blank'] = "<p>This function has no parameters defined.</p>";
  $tpl['FUNCTION.param:frame'] = 
          "<div class=\"variablelist\">\n" . 
          "<dl>\n" .
          "{FUNCTION.param:any}" .
          "</dl>\n</div>\n";
  $tpl['FUNCTION.param:any'] = 
          "  <dt><var class=\"parameter\">{PARAM}</var></dt>\n" .
          "  <dd>{PARAM.description}</dd>\n" .
          "{PARAM.reference?not_blank}" .
          "{PARAM.default?not_blank}";
  $tpl['PARAM.default?not_blank'] = 
          "  <dd><p>Default value of <var class=\"parameter\">{PARAM}</var> is\n" .
          "  <b class=\"expression\">{PARAM.default}</b>.</p></dd>\n";
  $tpl['PARAM.reference?blank'] = ""; 
  $tpl['PARAM.reference?not_blank'] = 
          "  <dd><p>Parameter <var class=\"parameter\">{PARAM}</var> is\n" .
          "  passed by reference.</p></dd>\n";
  // Return values
  $tpl['FUNCTION.return?not_blank'] = "{FUNCTION.return}";
  $tpl['FUNCTION.return?blank'] = "<p>This function has no return values defined.</p>";
  $tpl['FUNCTION.return:frame'] = 
          "<div class=\"variablelist\">\n" . 
          "<dl>\n" .
          "{FUNCTION.return:any}" .
          "</dl>\n</div>\n";
  $tpl['FUNCTION.return:any'] = 
          "  <dt><var class=\"type\">{RETURN.type}</var></dt>\n" .
          "  <dd>{RETURN.description}</dd>\n";
 
  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level and scope data
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $data["FUNCTION"] = $scope_data;
    $scope = "FUNCTION";
    $tpl['FUNCTION'] = $function;
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the documentation of the specified method.
 *
 * This method builds the documentation page for the specified method declared 
 * in the current source code file. A native XHTML template is hard-coded to render 
 * the page in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => class must be coded in PHP
 *    'ERRMSG:not_a_class' => method called on a file which is not a class
 *    'ERRMSG:method_not_found' => method not found in the current class 
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $method the method identifier
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the method "formated" documentation page
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getdoc_method($method, $view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP class
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  $type = empty($this->tree['main']['type']) ?
          "unknown" :
          $this->tree['main']['type'];
  if (!in_array($type, array("class", "abstract_class", "final_class", "interface"))) {
    $err = empty($template['ERRMSG:not_a_class']) ?
           "Current file isn't coding a class (type is $type)." :
           $template['ERRMSG:not_a_class'];
    return $err;
  }

  // Find the method
  if (array_key_exists($method, $this->tree['method'])) {
    $scope_data = $this->tree['method'][$method];
  } else {
    $err = empty($template['ERRMSG:method_not_found']) ?
           "Method $method not found in this file." :
           $template['ERRMSG:method_not_found'];
    return $err;
  }

  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<h1>{METHOD}</h1>\n" .
          "<p>{TREE.main.name}::{METHOD} -- {METHOD.description.short}</p>\n" .
          "{METHOD.source.member?not_blank}" .
          "{METHOD.info?not_blank}" .
          "{METHOD.info.warning?not_blank}" .
          "<h2>Description</h2>\n" .
          "<p>{METHOD_SYNTAX}</p>\n" .
          "{METHOD.description.long?not_blank}" .
          "{METHOD.info.experimental?not_blank}" .
          "<h2>Parameters</h2>\n" .
          "{METHOD.param?not_blank}" .
          "<h2>Return values</h2>\n" .
          "{METHOD.return?not_blank}" .
          "<h2>Source code</h2>\n" .
          "<p>The method is implemented at line {METHOD.source.line} in source code.</p>\n" .
          "<table border=\"0\" cellpadding=\"5\" bgcolor=\"#E0E0E0\"><tr><td>\n" .
          "{METHOD.source.code}" .
          "\n</td></tr></table>\n";
  $tpl['VIEW:public'] =
          "<h1>{METHOD}</h1>\n" .
          "<p>{TREE.main.name}::{METHOD} -- {METHOD.description.short}</p>\n" .
          "{METHOD.info.warning?not_blank}" .
          "<h2>Description</h2>\n" .
          "<p>{METHOD_SYNTAX}</p>\n" .
          "{METHOD.description.long?not_blank}" .
          "{METHOD.info.experimental?not_blank}" .
          "<h2>Parameters</h2>\n" .
          "{METHOD.param?not_blank}" .
          "<h2>Return values</h2>\n" .
          "{METHOD.return?not_blank}";
  // Syntax string
  $tpl['METHOD_SYNTAX'] = "{METHOD.source.return_type} <b class=\"methodname\">{METHOD}</b> ( {METHOD.source.parameters} )";
  // Infos
  $tpl['METHOD.info?not_blank'] = "{METHOD.info}";
  $tpl['METHOD.info:frame'] = 
          "<table class=\"methodinfo\">\n" .
          "<tr><td colspan=\"2\">Author informations</td></tr>\n" .
          "{METHOD.info:any}" .
          "</table>\n";
  $tpl['METHOD.info:any'] = "<tr><td>@{INFO%Keyword}&nbsp; </td><td> {INFO%Value}</td></tr>\n";
  $tpl['METHOD.info:exclude'] = array("experimental", "type", "warning", "public", 
                                      "private", "protected", "final", "static"); 
  // Description
  $tpl['METHOD.description.long?not_blank'] = 
          "<p>{METHOD.description.long}</p>\n";
  // Warning
  $tpl['METHOD.info.warning?not_blank'] = "<p><b>Warning: {METHOD.info.warning}</b></p>\n";
  // Experimental status
  $tpl['METHOD.info.experimental?not_blank'] =
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>This method is <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of this method, the name of this method, and anything\n" .
          "else documented about this method may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";  
  // Parameters
  $tpl['METHOD.param?not_blank'] = "{METHOD.param}";
  $tpl['METHOD.param?blank'] = "<p>This member function has no parameters defined.</p>";
  $tpl['METHOD.param:frame'] = 
          "<div class=\"variablelist\">\n" . 
          "<dl>\n" .
          "{METHOD.param:any}" .
          "</dl>\n</div>\n";
  $tpl['METHOD.param:any'] = 
          "  <dt><var class=\"parameter\">{PARAM}</var></dt>\n" .
          "  <dd>{PARAM.description}</dd>\n" .
          "{PARAM.reference?not_blank}" .
          "{PARAM.default?not_blank}";
  $tpl['PARAM.default?not_blank'] = 
          "  <dd><p>Default value of <var class=\"parameter\">{PARAM}</var> is\n" .
          "  <b class=\"expression\">{PARAM.default}</b>.</p></dd>\n";
  $tpl['PARAM.reference?blank'] = ""; 
  $tpl['PARAM.reference?not_blank'] = 
          "  <dd><p>Parameter <var class=\"parameter\">{PARAM}</var> is\n" .
          "  passed by reference.</p></dd>\n";
  // Return values
  $tpl['METHOD.return?not_blank'] = "{METHOD.return}";
  $tpl['METHOD.return?blank'] = "<p>This member function has no return values defined.</p>";          
  $tpl['METHOD.return:frame'] = 
          "<div class=\"variablelist\">\n" . 
          "<dl>\n" .
          "{METHOD.return:any}" .
          "</dl>\n</div>\n";
  $tpl['METHOD.return:any'] = 
          "  <dt><var class=\"type\">{RETURN.type}</var></dt>\n" .
          "  <dd>{RETURN.description}</dd>\n";
  // Statement
  $tpl['METHOD.source.member?not_blank'] = "<p>({METHOD.source.member} method)</p>\n";  

  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level and scope data
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $data["METHOD"] = $scope_data;
    $scope = "METHOD";
    $tpl['METHOD'] = $method;
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the documentation of the specified property.
 *
 * This method builds the documentation page for the specified property declared 
 * in the current source code file. A native XHTML template is hard-coded to render 
 * the page in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => class must be coded in PHP
 *    'ERRMSG:not_a_class' => method called on a file which is not a class
 *    'ERRMSG:property_not_found' => property not found in the current class 
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $property the property name
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the property "formated" documentation page
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getdoc_property($property, $view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP class
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  $type = empty($this->tree['main']['type']) ?
          "unknown" :
          $this->tree['main']['type'];
  if (!in_array($type, array("class", "abstract_class", "final_class", "interface"))) {
    $err = empty($template['ERRMSG:not_a_class']) ?
           "Current file isn't coding a class (type is $type)." :
           $template['ERRMSG:not_a_class'];
    return $err;
  }

  // Find the property
  if (array_key_exists($property, $this->tree['property'])) {
    $scope_data = $this->tree['property'][$property];
  } else {
    $err = empty($template['ERRMSG:property_not_found']) ?
           "Property $property not found in this file." :
           $template['ERRMSG:property_not_found'];
    return $err;
  }
  
  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<h1>{TREE.main.name}->{PROPERTY}</h1>\n" .
          "<p>{TREE.main.name}->{PROPERTY}{PROPERTY.info.type?not_blank} -- {PROPERTY.description.short}</p>\n" .
          "{PROPERTY.source.member?not_blank}" .
          "{PROPERTY.info?not_blank}" .
          "{PROPERTY.info.warning?not_blank}" .
          "{PROPERTY.description.long?not_blank}" .
          "{PROPERTY.info.type#undefined?blank}" .
          "{PROPERTY.info.experimental?not_blank}" .
          "<h2>Initial Value</h2>\n" .
          "{PROPERTY.source.initial?not_blank}" .
          "<h2>Source code</h2>\n" .
          "<p>The property is implemented at line {PROPERTY.source.line} in source code.</p>\n" .
          "<table border=\"0\" cellpadding=\"5\" bgcolor=\"#E0E0E0\"><tr><td>\n" .
          "{PROPERTY.source.code}" .
          "\n</td></tr></table>\n";
  $tpl['VIEW:public'] =
          "<h1>{TREE.main.name}->{PROPERTY}</h1>\n" .
          "<p>{TREE.main.name}->{PROPERTY}{PROPERTY.info.type?not_blank} -- {PROPERTY.description.short}</p>\n" .
          "{PROPERTY.info.warning?not_blank}" .
          "{PROPERTY.description.long?not_blank}" .
          "{PROPERTY.info.type#undefined?blank}" .
          "{PROPERTY.info.experimental?not_blank}" .
          "<h2>Initial Value</h2>\n" .
          "{PROPERTY.source.initial?not_blank}";
  // Infos
  $tpl['PROPERTY.info?not_blank'] = "{PROPERTY.info}";
  $tpl['PROPERTY.info:frame'] = 
          "<table class=\"propertyinfo\">\n" .
          "<tr><td colspan=\"2\">Author informations</td></tr>\n" .
          "{PROPERTY.info:any}" .
          "</table>\n";
  $tpl['PROPERTY.info:any'] = "<tr><td>@{INFO%Keyword}&nbsp; </td><td> {INFO%Value}</td></tr>\n";
  $tpl['PROPERTY.info:exclude'] = array("experimental", "type", "warning", "public", "private", 
                                        "protected", "final", "static"); 
  // Description
  $tpl['PROPERTY.description.long?not_blank'] = 
          "<h2>Description</h2>\n" .
          "<p>{PROPERTY.description.long}</p>\n";
  // Warning
  $tpl['PROPERTY.info.warning?not_blank'] = "<p><b>Warning: {PROPERTY.info.warning}</b></p>\n";
  // Experimental status
  $tpl['PROPERTY.info.experimental?not_blank'] =
          "<table class=\"warning\" border=\"1\" width=\"100%\">\n" .
          "<tr><td align=\"center\"><b>Warning</b></td></tr>\n" .
          "<tr><td align=\"left\"><p>This property is <span class=\"emphasis\">" .
          "<i class=\"emphasis\">EXPERIMENTAL</i></span>.\n" .
          "The behaviour of this property, the name of this property, and anything\n" .
          "else documented about this property may change without notice in a future\n" .
          "release.</p></td></tr>\n</table>\n";
  // Initial value
  $tpl['PROPERTY.source.initial?not_blank'] =
          "<p><code class=\"value\">{PROPERTY.source.initial}</code></p>\n";
  $tpl['PROPERTY.source.initial?blank'] =
          "<p>This property has no initial value defined</p>\n";
  // Type
  $tpl['PROPERTY.info.type?not_blank'] = ' ({PROPERTY.info.type})';
  // Type undefined
  $tpl['PROPERTY.info.type#undefined?not_blank'] = "";
  $tpl['PROPERTY.info.type#undefined?blank'] =
          "<p>This property has no type explicitly defined</p>\n";
  // Statement
  $tpl['PROPERTY.source.member?not_blank'] = "<p>({PROPERTY.source.member} property)</p>\n";  

 // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level and scope data
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $data["PROPERTY"] = $scope_data;
    $scope = "PROPERTY";
    $tpl['PROPERTY'] = $property;
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the navigation links of of the current class.
 *
 * This method builds the documentation navigation links for the class found 
 * in the current source code file. A native XHTML template is hard-coded to 
 * render the page in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => class must be coded in PHP
 *    'ERRMSG:not_a_class' => method called on a file which is not a class
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the navigation "formated" links
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getnav_class($view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP code
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  $type = empty($this->tree['main']['type']) ?
          "unknown" :
          $this->tree['main']['type'];
  if (!in_array($type, array("class", "abstract_class", "final_class", "interface"))) {
    $err = empty($template['ERRMSG:not_a_class']) ?
           "Current file isn't coding a class (type is $type)." :
           $template['ERRMSG:not_a_class'];
    return $err;
  }
  
  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<ul id=\"manualtoc\">\n" .
          "{HEADER_HOME}" .
          "{HEADER_UP}" .
          "{TREE.method?not_blank}" .
          "{TREE.property?not_blank}" .
          "</ul>\n";
  // Public view
  $tpl['VIEW:public'] =
          "<ul id=\"manualtoc\">\n" .
          "{HEADER_HOME}" .
          "{HEADER_UP}" .
          "{TREE.public_method?not_blank}" .
          "{TREE.public_property?not_blank}" .
          "</ul>\n";
  // Headers
  $tpl['HEADER_HOME'] = 
          " <li class=\"header home\"><a href=\"" . $_SERVER['PHP_SELF'] . "?view={VIEW}\">Manual</a></li>\n";
  $tpl['HEADER_UP'] = 
          " <li class=\"header up\"><a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}" .
          "&view={VIEW}\">{TREE.main.name}</a></li>\n";
  // Methods
  $tpl['TREE.method?not_blank'] = "{TREE.method}";
  $tpl['TREE.method:frame'] = 
          "{TREE.method:any}";
  $tpl['TREE.method:any'] = 
          " <li><a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&method={METHOD}" .
          "&view={VIEW}\">{METHOD}</a></li>\n";
  // Properties
  $tpl['TREE.property?not_blank'] = "{TREE.property}";
  $tpl['TREE.property:frame'] = 
          "{TREE.property:any}";
  $tpl['TREE.property:any'] = 
          " <li><a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&property={PROPERTY}" .
          "&view={VIEW}\">{TREE.main.name}->{PROPERTY}</a></li>\n";
  // Public methods
  $tpl['TREE.public_method?not_blank'] = "{TREE.public_method}";
  $tpl['TREE.public_method:frame'] = 
          "{TREE.public_method:any}";
  $tpl['TREE.public_method:any'] = 
          " <li><a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&method={PUBLIC_METHOD}" .
          "&view={VIEW}\">{PUBLIC_METHOD}</a></li>\n";
  // Public properties
  $tpl['TREE.public_property?not_blank'] = "{TREE.public_property}";
  $tpl['TREE.public_property:frame'] = 
          "{TREE.public_property:any}";
  $tpl['TREE.public_property:any'] = 
          " <li><a href=\"" . $_SERVER['PHP_SELF'] . "?class={TREE.main.name}&property={PUBLIC_PROPERTY}" .
          "&view={VIEW}\">{TREE.main.name}->{PUBLIC_PROPERTY}</a></li>\n";

  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $scope = "TREE";
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }
  
  return $outh;
}

/**
 * Return the navigation links of of the current library.
 *
 * This method builds the documentation navigation links for the library found 
 * in the current source code file. A native XHTML template is hard-coded to 
 * render the page in the php.net manual style.
 *
 * The $template parameter can be used to redefine some custom template elements
 * or to redefine the whole template. Thus you can create a template for CHM, 
 * PDF or XML output asuming you set the template FLAG:smart_display "off".
 * 
 * By default, the documentation is returned rendered, but the MDOC_RETURN_TEMPLATE 
 * flag can be set to return the template "as is" after customization with the 
 * elements passed in $template parameter.
 *
 * To build your own template, see the template technical reference page.
 *
 * This method implements the following error messages:
 *    'ERRMSG:language_not_supported' => library must be coded in PHP
 *    'ERRMSG:not_a_library' => method called on a file which is not a library
 *
 * If not already done, this method builds the documentation internal tree.
 *
 * @param (string) $view set to 'public' to view only API usage details, 
 *                       set to 'author' (default value) to view all details 
 * @param (array) $template optional custom template hooks
 * @param (int) $flag set to MDOC_RETURN_TEMPLATE to get the template associative
 *                    array which may be useful to get the hard-coded template
 *                    elements or to help in building custom elements,
 *                    set to MDOC_RETURN_NORMAL (default value) to get a nice 
 *                    document page rendered within native or custom template
 * @return (string) the navigation "formated" links
 *         (string) an error message if the documentation page can't be built
 *         (array) the template array if the flag MDOC_RETURN_TEMPLATE is set
 */
public function getnav_library($view = "author", $template = array(), $flag = MDOC_RETURN_NORMAL) {

  // Parse file into documentation tree if not done yet
  if (empty($this->tree)) $this->parse(); 
  
  // Ensure working on a valid PHP code
  $language = empty($this->tree['main']['language']) ?
              "unknown" :
              $this->tree['main']['language'];
  if ($language != "PHP") {
    $err = empty($template['ERRMSG:language_not_supported']) ?
           "Current code language ($language) isn't supported." :
           $template['ERRMSG:language_not_supported'];
    return $err;
  }
  $type = empty($this->tree['main']['type']) ?
          "unknown" :
          $this->tree['main']['type'];
  if ($type != "library") {
    $err = empty($template['ERRMSG:not_a_library']) ?
           "Current file isn't coding a library (type is $type)." :
           $template['ERRMSG:not_a_library'];
    return $err;
  }
      
  // Set hard-coded template, author view first
  $tpl['VIEW:author'] =
          "<ul id=\"manualtoc\">\n" .
          "{HEADER_HOME}" .
          "{HEADER_UP}" .
          "{TREE.function?not_blank}" .
          "</ul>\n";
  // Public view
  $tpl['VIEW:public'] =
          "<ul id=\"manualtoc\">\n" .
          "{HEADER_HOME}" .
          "{HEADER_UP}" .
          "{TREE.function?not_blank}" .
          "</ul>\n";
  // Headers
  $tpl['HEADER_HOME'] = 
          " <li class=\"header home\"><a href=\"" . $_SERVER['PHP_SELF'] . "?view={VIEW}\">Manual</a></li>\n";
  $tpl['HEADER_UP'] = 
          " <li class=\"header up\"><a href=\"" . $_SERVER['PHP_SELF'] . "?lib={TREE.main.name}" .
          "&view={VIEW}\">{TREE.main.name}</a></li>\n";
  // Functions
  $tpl['TREE.function?not_blank'] = "{TREE.function}";
  $tpl['TREE.function:frame'] = 
          "{TREE.function:any}";
  $tpl['TREE.function:any'] = 
          " <li><a href=\"" . $_SERVER['PHP_SELF'] . "?lib={TREE.main.name}&function={FUNCTION}" .
          "&view={VIEW}\">{FUNCTION}</a></li>\n";

  // Overwrite hard-coded elements with custom template elements
  foreach ($template as $key => $element) {
    $tpl[$key] = $element;
  }

  // Return the template if requested
  if ($flag && MDOC_RETURN_TEMPLATE) {
    return $tpl;
  }
  
  // Fill template in respect with the view level
  if (array_key_exists("VIEW:$view", $tpl)) {
    $data["TREE"] = $this->tree;
    $scope = "TREE";
    $tpl['VIEW'] = $view;
    $outh = $this->fetch($tpl["VIEW:$view"], $tpl, $scope, $data);
  } else {
    trigger_error("requested view <b>\"$view\"</b> is not defined in template", E_USER_WARNING);
    return "";  
  }

  return $outh;
}

/*****************************************************************************/
/*                                                                           */
/*                          Raw output methods                               */
/*                                                                           */
/*****************************************************************************/

/**
 * Return all or parts of the internal structure documentation.
 *
 * While this method is public, it's been made available essentialy for 
 * testing purpose such as regression tests for Mdoc code maintenance.
 *
 * @param (string) $branch the documentation tree branch to return, the default 
 *                         value is an empty string and then the whole tree is 
 *                         returned
 * @return (array) a documentation tree branch, if the branch doesn't exists 
 *                 an empty array is returned (without notice) 
 */
public function get_tree($branch = "") {

  // Build tree if not yet done
  if (empty($this->tree)) $this->parse();
  
  // Print  or return tree
  if (empty($branch)) {
    return $this->tree;  // by default return the whole tree
  } elseif (!empty($this->tree[$branch])) {
    return $this->tree[$branch];
  } else {
    return array();  // branch not found
  }
}
 
/*****************************************************************************/
/*                                                                           */
/*                         Smart display methods                             */
/*                                                                           */
/*****************************************************************************/

/**
 * Format source code comment as smart (X)HTML ouput.
 *
 * This method calls the appropriate formating method regarding the canonical
 * path of the template element to display.
 *
 * @param (string) $comment the comment extracted from the source code
 * @param (string) $path the canonical path of the comment to display
 * @return (string) the source code comment formated for output, if the
 *                  comment is not a scalar value then the template
 *                  element is returned (path between brackets) 
 */
private function display($comment, $path) {

  // Must be a scalar value
  if (is_scalar($comment)) {
    if (preg_match('/(value|source\.value|source\.initial)$/', $path)) {
      $smart = $this->display_code_expression($comment);
    } elseif (preg_match('/(source\.code)$/', $path)) {
      $smart = $this->display_code_statement($comment);
    } elseif (preg_match('/(description\.long|description)$/', $path)) {
      $smart = $this->display_long_description($comment);
    } elseif (preg_match('/(info)$/', $path)) {
      $smart = $this->display_info_comment($comment);
    } else {
      $smart = $this->display_comment($comment);
    }
  } elseif (is_array($comment))  {
    trigger_error('element <b>{'.$path.'}</b> value is an array', E_USER_NOTICE);    
    $smart = '{'.$path.'}';        
  } else {
    trigger_error('element <b>{'.$path.'}</b> value type is not supported (not a scalar or array)', E_USER_NOTICE);
    $smart = '{'.$path.'}';        
  }

  return $smart;
}

/**
 * Format code expressions as smart (X)HTML ouput.
 *
 * This method tries to reduce code indenting to get a nice display in <PRE> elements,
 * so it's convenient for value display (property and constant values).
 *
 * This methods supports PHP expressions.
 *
 * Smart indenting:
 *
 * array(
 *                          'next' => "<hr class=\"next\" />",
 *                          'nl' => "<br />",
 *                          'trap' => "<b>a ; to trap value parsing</b>",
 *                        )
 *
 * is converted to:
 *
 * array(
 *      'next' =&gt; &quot;&lt;hr class=\&quot;next\&quot; /&gt;&quot;,
 *      'nl' =&gt; &quot;&lt;br /&gt;&quot;,
 *      'trap' =&gt; &quot;&lt;b&gt;a ; to trap value parsing&lt;/b&gt;&quot;,
 *    )      
 *
 * @param (string) $code the code to reduce indent 
 * @return (string) the source code formated for output
 */
private function display_code_expression($code) {

  // Onluy PHP support is imlmented yet
  if ($this->tree['main']['language'] != "PHP") return $code;
  
  // Initiate smart output
  $smart = $code;
  
  // Arrange splitted strings
  $smart =  preg_replace('!\s*((["\'])[^\n]+\\2\s*\.)\s*\n!s', "\\1\n", $smart);
  $smart =  preg_replace('!\n\s*((["\'])[^\n]+\\2)\s*$!s', "\n\\1", $smart);
  
  // Preserve HTML entities
  $smart = htmlentities($smart);

  return $smart;
}

/**
 * Format code statements as smart (X)HTML ouput.
 *
 * This method supports PHP code highlighting:
 *
 * "const START = 0;  // this comment isn't considered for documenting"
 *
 * is displayed as
 *
 * <code><font color="#000000"> 
 * <font color="#007700">&lt;?</font>
 * <font color="#007700">const </font><font color="#0000BB">START </font>
 * <font color="#007700">= </font>
 * <font color="#0000BB">0</font><font color="#007700">;&nbsp;&nbsp;</font>
 * <font color="#0000BB">?&gt;</font>
 * </font>
 * </code>
 *
 * @param (string) $code the code to reduce indent 
 * @return (string) the source code formated for output
 */
private function display_code_statement($code) {

  // Onluy PHP support is imlmented yet
  if ($this->tree['main']['language'] != "PHP") return $code;
  
  // Initiate smart output
  $smart = $code;
  
  // Nice PHP code. For other codes, we'll use htmlentities()
  $smart = highlight_string("<?php\n\n$smart\n\n?>", true);

  return $smart;
}

/**
 * Format source code comment as smart (X)HTML ouput.
 *
 * This method is the default display one, it preserves HTML entities.
 *
 * @param (string) $comment the comment extracted from the source code
 * @return (string) the source code comment formated for output
 */
private function display_comment($comment) {

  // Preserve HTML entities
  $smart = htmlentities($comment);
  
  return $smart;
}

/**
 * Format source code "@info comment" as smart (X)HTML ouput.
 *
 * This method keeps the new line chars display for a smart @info
 * comment display in a list.
 *
 * @param (string) $comment the comment extracted from the source code
 * @return (string) the source code comment formated for output
 */
private function display_info_comment($comment) {

  // Preserve HTML entities
  $smart = htmlentities($comment);
  // Keeps new lines
  $smart = preg_replace('!\n!', "<br />\n", $smart);

  return $smart;
}

/**
 * Format source code "long description" comment as smart (X)HTML ouput.
 *
 * This method converts source code comments into HTML trying to keep a display
 * close to the plain test format (keep identation, list, etc.). It suits well
 * the display of long descriptions.
 *
 * Smart conversions:
 *
 * 1) blank lines are converted to <br />\n
 *
 * 2) an unordered list is a sequence of lines starting all with same -+* bullet,
 * indented on same column at least 2 spaces from margin star, so
 *
 *     - list item 1
 *       on two lines
 *     - second item
 *     - another list item
 *            + sublist it1
 *            + sublist it2
 *                 * subsub A
 *                 * subsub B
 *                 * subsub C
 *     - another ?
 *       again!
 *
 * is translate into an HTML unordered list <ul></ul> (max 8 items by list, 
 * nested up to 4 levels)
 *
 * 3) a list of array key alternatives is a sequence of lines starting with  keys 
 * in '' or [] or digits, indented on same column at least 2 spaces from margin star
 * and spread on 16 items maximum
 *
 *    'alt1' => first alternative
 *              array values can be multilined
 *    'alt2' => other
 *    'alt3' => last
 *
 * digit keys example:
 *
 *     0 => arrays can be nested
 *     1 => like this 
 *
 * simplified print_r (also var_export and var_dump) style example:
 *
 *     [print_color] => 
 *             // keys in this array are the method names
 *             [param] => 
 *                 [structfile] => 
 *                          [type] => 
 *                          [description] => 
 *                  [output] => 
 *                          [default] => 
 *                          [type] => 
 *                          [description] => 
 *             [return] => 
 *                  [0] => 
 *                          [type] => 
 *                          [description] => 
 *
 * is translated in an HTML <pre></pre> indentation
 *
 * @param (string) $comment the comment extracted from the source code
 * @return (string) the source code comment formated for output
 */
private function display_long_description($comment) {
  
  // Initialize conversion buffer
  $smart = $comment;
  
  // First of all tranlates HTML characters
  $smart = htmlentities($smart);

  // Blanks lines converted to BR
  $smart = preg_replace('!^\s*$!m', "<br />\n<br />", $smart);

  // Add few blank chars to keep following Perl regex simple (avoid $ -last char- testing)
  $smart = $smart . "  \n";
  
  // Unordered lists starting with - * or +, at least 2 items and maximum 8
  // and lists can be nested on 4 levels
  for ($i = 1; $i <= 4; $i++) {
    $iul = $i;    // <ul> indentation
    $ili = $i+1;  // <li> indentation
    $smart = preg_replace('!\n(\s{2,})([-*+])\x20((?:[^\n]*)\n(?:\1\s\s[^\n]*\n)*)'.
                '\1\2\x20((?:[^\n]*)\n(?:\1\s\s[^\n]*\n)*)'.
                str_repeat('(?:\1\2\x20((?:[^\n]*)\n(?:\1\s\s[^\n]*\n)*))?', 6) .
                '!',
                "\n" . str_repeat("  ", $iul) . "<ul>" .
                "\n" . str_repeat("  ", $ili) . "<li>\\3" . str_repeat("  ", $ili) . "</li>" .
                "\n" . str_repeat("  ", $ili) . "<li>\\4" . str_repeat("  ", $ili) . "</li>" .
                "\n" . str_repeat("  ", $ili) . "<li>\\5" . str_repeat("  ", $ili) . "</li>" .
                "\n" . str_repeat("  ", $ili) . "<li>\\6" . str_repeat("  ", $ili) . "</li>" .
                "\n" . str_repeat("  ", $ili) . "<li>\\7" . str_repeat("  ", $ili) . "</li>" .
                "\n" . str_repeat("  ", $ili) . "<li>\\8" . str_repeat("  ", $ili) . "</li>" .
                "\n" . str_repeat("  ", $ili) . "<li>\\9" . str_repeat("  ", $ili) . "</li>" .
                "\n" . str_repeat("  ", $ili) . "<li>\\10" . str_repeat("  ", $ili) . "</li>" .
                "\n" . str_repeat("  ", $iul) . "</ul>\n",                           
                $smart);
  }
  $smart = preg_replace('!\n\s*<li>\s*</li>!', "", $smart);  // cleanup: remove empty items
  $smart = preg_replace('!\s*<br />\s*<br />(\s*)<ul>!', "\\1<ul>", $smart);  // cleanup: useless <br /> before <ul>
  $smart = preg_replace('!</ul>\s*<br />\s*<br />!', "</ul>", $smart);  // cleanup: useless <br /> after </ul>
  
  // Array with key alternatives lines starting with keys in '' or [] or digits, 16 items maximum
  $smart = preg_replace('!(\n(\s{2,})([\'\[].*?[\'\]]|\d+)\s*.*=.*?\n(?:\2\s\s[^\n]*\n)*)'.
                '(\2([\'\[].*?[\'\]]|\d+)\s*.*=.*?\n(?:\2\s\s[^\n]*\n)*)?' .
                str_repeat('(\2([\'\[].*?[\'\]]|\d+)\s*.*=.*?\n(?:\2\s\s[^\n]*\n)*)?', 14) .
                '!',
                "\n<pre>\\1\\4\\6\\8\\10\\12\\14\\16\\18\\20\\22\\24\\26\\28\\30\\32</pre>\n",                           
                $smart);
  $smart = preg_replace('!<br />\s*<br />\s*<pre>!', "<pre>", $smart);  // cleanup: useless <br /> before <pre>
  $smart = preg_replace('!</pre>\s*<br />\s*<br />!', "</pre>", $smart);  // cleanup: useless <br /> after </pre>

  // Remove the few blank chars added for Perl simplicity
  $smart = preg_replace('!  \n$!s', "", $smart);
  
  return $smart;
}

/*****************************************************************************/
/*                                                                           */
/*                       Template fetching methods                           */
/*                                                                           */
/*****************************************************************************/

/**
 * Fetch recursively all template variables in a subject string.
 *
 * This method parses a string subject to extract template 
 * references from a subject string and makes all template
 * variable substitutions possible, and repeat this process
 * several times to solve all recursive template variables. 
 *
 * For a complete description of template support, see the template
 * technical reference page.
 *
 * @param (string) $subject the subject to resolve template references 
 * @param (array) $template the template is an associative array of elements
 * @param (string) $scope   the resolution scope (eg: TREE or CONSTANT)
 * @param (array) $data     the data tree to solve for data references
 * @return (string) the mofidied subject
 */
private function fetch($subject, $template, $scope, $data) {

  // Set maximum number of pass
  $pass_todo = 10;

  // Initiate stack to memorize subject evolution
  $stack = array();
  
  // Perform fetch passes
  while ($pass_todo > 0) {

    // Do one pass and return if job done
    $new_subject = $this->fetch_elements($subject, $template, $data);
    if ($new_subject == $subject) {
      // Fetch data references
      $new_subject = $this->fetch_data($new_subject);    
      return $new_subject;
    }
    // Before next pass, check complex infinite loop
    if (in_array($new_subject, $stack)) {
      trigger_error("there may be template elements which indirectly refers to themselves as an infinite loop", E_USER_NOTICE);
      return $new_subject;
    }
    // Before next pass, add new result on top of stack
    array_unshift($stack, $new_subject);  
    // Before next pass, prepare subject
    $subject = $new_subject;
    // Now, one pass less to do
    $pass_todo--;
  }

  // Notice maximum pass as infinite loop
  trigger_error("there may be a template element which refer to itself as an infinite loop", E_USER_NOTICE);  
  return $new_subject;
}

/**
 * Fetch a template array element in its scope.
 *
 * This method parses the frame and item strings associated to the template
 * element to extract template references and makes all template variable 
 * substitutions possible in one pass. 
 *
 * Array item values can be array as well, then they're fetched recursively
 * in their sub-scope (while accessing upper scopes as well).
 *
 * This methods supports elements :first, :any and :last to format items.
 *
 * For a complete description of template support, see the template
 * technical reference page.
 *
 * @param (string) $reference the template reference
 * @param (array) $template   the template is an associative array of elements
 * @param (array) $data       the data tree to solve for data references
 * @return (string) the array frame element filled with resolved item elements
 */
private function fetch_array($reference, $template = array(), $data) {

  // Must have :frame and :list related template elements
  if (array_key_exists("$reference:frame", $template)) {
    $frame = $template["$reference:frame"];
  } else {
    trigger_error('element :frame missing in template for <b>{'.$reference.'}</b> array', E_USER_NOTICE);
    return '{'.$reference.'}';
  }
  if (array_key_exists("$reference:any", $template)) {
    $any = $template["$reference:any"];
  } else {
    trigger_error('element :any missing in template for <b>{'.$reference.'}</b> array', E_USER_NOTICE);
    return '{'.$reference.'}';
  }

  // May have :first and :last related template elements
  $first = array_key_exists("$reference:first", $template) ?
           $template["$reference:first"] :
           null;
  $last =  array_key_exists("$reference:last", $template) ?
           $template["$reference:last"] :
           null;

  // Extract current scope from reference
  if (preg_match('/([^\.]+)(#.*)$/', $reference, $m)
      || preg_match('/([^\.]+)$/', $reference, $m)) {
    $scope = strtoupper($m[1]);
    $frame_id = empty($m[2]) ? "" : $m[2];
  } else {
    trigger_error('failed to extract <b>{'.$reference.'}</b> array scope from reference', E_USER_NOTICE);
    return '{'.$reference.'}';
  }
  $scope_values = $data[$scope];

  // Walk the array to build the list of formated values
  $subject_list = "";
  $list_index = 0;
  $last_item = count($scope_values);
  foreach($scope_values as $key => $value) {
    $list_index++;
    // Support :first, :any and :last
    if ($list_index == 1 && !is_null($first)) {
      $item = $first;
    } elseif ($list_index == $last_item && !is_null($last)) {
      $item = $last;
    } else {
      $item = $any;
    }
    // Array key display isn't altered
    $item = str_replace('{'.$scope.'%Keyword}', $key, $item); 
    // Scalar value, eg: [cvs] 
    if (is_scalar($value)) {
      $item = str_replace('{'.$scope.'%Value}', $this->display($value, "$scope.$key"), $item); 
    // Array value, eg: [function]
    } elseif (is_array($value))  {
      // Extract scope references from item template 
      if (preg_match_all('/\{('.$scope.'\..*?)\}/', $item, $m)) {
        $subrefs = array_unique($m[1]);
        foreach ($subrefs as $tref) {
          $scope_data = $data;
          $scope_data[$scope] = $value;
          // Allow 10 levels ind depth of recursive template elements
          for ($i = 0; $i < 10; $i++) {
            $item = $this->fetch_elements($item, $template, $scope_data);
          }
        }
      }
      // Scope id display isn't altered
      $item = str_replace('{'.$scope.'}', $key, $item); 
    } else {
      // Array value type is not supported
      trigger_error('element <b>{'.$scope.$frame_id.'.'.$key.'}</b> value type is not supported (not a scalar or array)', E_USER_NOTICE);
    }
    $subject_list .= $item;
  }
  
  // Insert the list in the frame
  $subject_frame = str_replace('{'.$reference.':any}', $subject_list, $frame);

  return $subject_frame;
}

/**
 * Fetch all data references in a subject string.
 *
 * To avoid confusion between template variable and tree content (which could
 * contain {TEMPLATE_LIKE} strings), data values are stored in a stack to be
 * fetch at the very end of fetching process.
 *
 * Every {{{Mdoc:DATA:integer}}} is replaced by $this->fetch_data[integer]
 * value.
 *
 * For a complete description of template support, see the template
 * technical reference page.
 *
 * @param (string) $subject the subject to fetch data references 
 * @return (string) the subject filled with resolved data elements
 */
private function fetch_data($subject) {

  foreach ($this->fetch_data as $i => $data) {
    $subject = str_replace('{{{Mdoc:DATA:'.$i.'}}}', $data, $subject);
  }
  
  return $subject;
}

/**
 * Fetch all template elements found in a subject.
 *
 * This method parses a string subject to extract template 
 * references and makes all template variable substitutions 
 * possible in one pass. 
 *
 * For a complete description of template support, see the template
 * technical reference page.
 *
 * @param (string) $subject the subject to resolve template references 
 * @param (array) $template the template is an associative array of elements
 * @param (array) $data     the data tree to solve for data references
 * @return (string) the mofidied subject
 */
private function fetch_elements($subject, $template, $data) {

  // Extract all template variables
  if (preg_match_all('/\{(([A-Z][A-Z0-9_]*)(\.[a-zA-Z0-9_\.]+)?(#[a-z0-9_]+)?(\?[=\!a-zA-Z0-9_-]+)?)\}/', $subject, $m)) {
    $template_references = array_unique($m[1]);
  }
  
  // Done if no template variables to process
  if (empty($template_references)) return $subject;

  // Initiate subject string to process
  $solved_subject = $subject;
  
  // Process every template element
  foreach ($template_references as $tref) {
    // Set current template variable
    $tvar = '{'.$tref.'}';
    // Get element informations
    $tinfos = $this->fetch_getinfos($tref, $template, $data);
    // Resolve if a redirection (within the template array)
    if (!empty($tinfos['redirection'])) {
      $tredir = $tinfos['redirection'];
      // Process redirection
      if (   ($tredir == "?blank" && $tinfos['?blank']) 
          || ($tredir == "?not_blank" && !$tinfos['?blank']) 
          || ($tredir == "?empty" && $tinfos['?empty']) 
          || ($tredir == "?not_empty" && !$tinfos['?empty']) 
          || ($tredir == "?exists" && $tinfos['?exists']) 
          || ($tredir == "?not_exists" && !$tinfos['?exists']) ) {
        // Try template substitution
        if (array_key_exists($tref, $template)) {
          $solved_subject = str_replace($tvar, $template[$tref], $solved_subject);
        } else {
          trigger_error("element <b>$tvar</b> not found in template", E_USER_NOTICE);
        }
      }
      // Process **complementray** redirection, with clear by default
      if (   ($tredir == "?blank" && !$tinfos['?blank']) 
          || ($tredir == "?empty" && !$tinfos['?empty']) 
          || ($tredir == "?exists" && !$tinfos['?exists']) ) {
        // Try **complementray** template substitution
        $comp_tref = str_replace("?", "?not_", $tref);
        if (array_key_exists($comp_tref, $template)) {
          $solved_subject = str_replace($tvar, $template[$comp_tref], $solved_subject);
        } else {
          $solved_subject = str_replace($tvar, "", $solved_subject);
        }
      }
      // Negative to positive as well
      if (   ($tredir == "?not_blank" && $tinfos['?blank']) 
          || ($tredir == "?not_empty" && $tinfos['?empty']) 
          || ($tredir == "?not_exists" && $tinfos['?exists']) ) {
        // Try **complementray** template substitution
        $comp_tref = str_replace("?not_", "?", $tref);
        if (array_key_exists($comp_tref, $template)) {
          $solved_subject = str_replace($tvar, $template[$comp_tref], $solved_subject);
        } else {
          $solved_subject = str_replace($tvar, "", $solved_subject);
        }
      }
      // Process ?= redirection and its complement
      if (preg_match('/^\?=(.+)/', $tredir, $m)) {
        $comp_tref = preg_replace('/^(.*?)\?=/', "\\1?!=", $tref);
        if ($tinfos[$tredir]) {
          $solved_subject = str_replace($tvar, $template[$tref], $solved_subject);
        } elseif (array_key_exists($comp_tref, $template)) {
          $solved_subject = str_replace($tvar, $template[$comp_tref], $solved_subject);
        } else {
          $solved_subject = str_replace($tvar, "", $solved_subject);
        }
      }
      // Process ?!= redirection and its complement
      if (preg_match('/^\?\!=(.+)/', $tredir, $m)) {
        $comp_tref = preg_replace('/^(.*?)\?\!=/', "\\1?=", $tref);
        if ($tinfos[$tredir]) {
          $solved_subject = str_replace($tvar, $template[$tref], $solved_subject);
        } elseif (array_key_exists($comp_tref, $template)) {
          $solved_subject = str_replace($tvar, $template[$comp_tref], $solved_subject);
        } else {
          $solved_subject = str_replace($tvar, "", $solved_subject);
        }
      }
    // Resolve if a template reference
    } elseif ($tinfos['type'] == "template") {
      if ($tinfos['?exists']) {
        $tstring = $tinfos['value'];  // display not altered for template elements
        $solved_subject = str_replace($tvar, $tstring, $solved_subject);
      }
    // Resolve if a data reference
    } elseif ($tinfos['type'] == "data") {
      if ($tinfos['?exists'] && is_scalar($tinfos['value'])) {
///        $tstring = $this->display($tinfos['value'], $tinfos['path']);
///        $solved_subject = str_replace($tvar, $tstring, $solved_subject);
        // Data value is stored in fetch stack until last pass
        $solved_subject = str_replace($tvar, "{{{Mdoc:DATA:" . count($this->fetch_data) . "}}}", $solved_subject);
        $this->fetch_data[] = $this->display($tinfos['value'], $tinfos['path']);
      } elseif ($tinfos['?exists'] && is_array($tinfos['value'])) {
        $data[$this->fetch_getscope($tref)] = $tinfos['value']; // add scope values to data
        $tstring = $this->fetch_array($tref, $template, $data);
        $solved_subject = str_replace($tvar, $tstring, $solved_subject);
      } elseif ($tinfos['?exists']) {
        trigger_error("element <b>$tvar</b> type is not supported (not a scalar or array)", E_USER_NOTICE);
      }
    // Notice a missing resolution case
    } else {
      trigger_error("failed to fetch <b>$tvar</b> element informations", E_USER_NOTICE);
    }
  }

  return $solved_subject;
}

/**
 * Return the informations of a template element.
 *
 * This informations is returned in the following structure: 
 *
 *     'reference'   => 'TREE.main.info#values?not_blank',
 *     'branch'      => 'TREE',
 *     'path'        => 'main.info',
 *     'label'       => '#values',
 *     'redirection' => '?not_blank',
 *     'element'     => 'TREE.main.info#values',
 *     'type'        => 'data',
 *     '?blank'      => false,
 *     '?empty'      => false,
 *     '?exists'     => true,
 *     '?=avalue'    => false,
 *     'value'       => array (
 *                        'warning' => 'this version is unstable',
 *                        'date' => '28 Aug 2005',
 *                      ),
 *
 * @param (string) $reference the template reference
 * @param (array) $template   the template is an associative array of elements
 * @param (array) $data       the data tree to solve for data references
 * @return (string) the element value or redirected, or the elemlent reference if can't be solved
 */
private function fetch_getinfos($reference, $template = array(), $data) {

  // Initiate information table 
  $informations = array();

  // Parse element reference
  // -- reference
  // -- branch
  // -- path
  // -- label
  // -- redirection
  // -- element
  $informations['reference'] = $reference;
  if (preg_match('/^([A-Z][A-Z0-9_]*)(?:\.([a-zA-Z0-9_\.]+))?(#[a-z0-9_]+)?(\?[=\!a-zA-Z0-9_-]+)?/', $reference, $m)) {
    $informations['branch'] = $m[1];
    $informations['path'] = empty($m[2]) ? "" : $m[2];
    $informations['label'] = empty($m[3]) ? "" : $m[3];
    $informations['redirection'] = empty($m[4]) ? "" : $m[4];
    // Element reference may be required to access :exclude element
    $element = $informations['branch'];
    $element .= empty($informations['path']) ? "" :
                "." . $informations['path'];
    $element .= $informations['label'];
    $informations['element'] = $element;
  } else {
    $informations['type'] = "invalid";
    // Done with invalid template reference
    return $informations;
  }

  // Try to find the element in the data
  // -- type
  if (array_key_exists($informations['branch'], $data)) {
    $informations['type'] = "data";
  // Or it's a template element
  } else {
    $informations['type'] = "template";
  }

  // Find the value
  // --value
  // Check if the element is the scope value (so is no path)
  if ($informations['type'] == "data" && 
      empty($informations['path']) && array_key_exists($reference, $template)) {
    $informations['value'] =  $template[$reference];
  } elseif ($informations['type'] == "data") {
    // Walk the path in data tree
    $node_chain = explode(".", $informations['path']);
    $tree_to_walk = $data[$informations['branch']];
    foreach ($node_chain as $node) {
      if (isset($tree_to_walk[$node])) { 
        $tree_to_walk = $tree_to_walk[$node];
        // The walk will end here if the node exists
        // and continue to next node
        $informations['value'] = $tree_to_walk;
        continue;
      } else {
        // Walk done with missing "data" element
        // so the 'value' is not set 
        if (array_key_exists('value', $informations)) {
          unset($informations['value']);
        }
      }
    }
  } elseif (array_key_exists($element, $template)) {
    // Load the value from template
    $informations['value'] = $template[$element];
  }

  // Process array value exclusions if any
  // -- :exclude
  if (array_key_exists('value', $informations) && is_array($informations['value'])
      && !empty($template["$element:exclude"])) {
    $informations[':exclude'] = $template["$element:exclude"];
    // Reset array and push in again
    $all = $informations['value'];
    $informations['value'] = array();
    foreach ($all as $key => $val) {
      if (!in_array($key, $template["$element:exclude"])) {
        $informations['value'][$key] = $val;
      }
    }
  }
  
  // Set standard redirections
  // -- ?blank
  // -- ?empty
  // -- ?exists
  if (!array_key_exists('value', $informations)) {
    // Process inexistant value
    $informations['?blank'] = true;
    $informations['?empty'] = true;
    $informations['?exists'] = false;
  } else {
    $value = $informations['value'];
    $informations['?blank'] = empty($value) && !is_numeric($value);
    $informations['?exists'] = true;
    $informations['?empty'] = empty($value);
  }  

  // Set equal redirection
  // -- ?=value
  // -- ?!=value
  if (!empty($informations['redirection'])) {
    $redir = $informations['redirection'];
    if (preg_match('/^\?=(.+)/', $redir, $m)) {
      if (!array_key_exists('value', $informations)) {
        $informations[$redir] = false;
      } elseif (is_array($informations['value'])) {
        $informations[$redir] = false;      
      } else {
        $informations[$redir] = ($m[1] == $informations['value']);
      }
    } elseif (preg_match('/^\?\!=(.+)/', $redir, $m)) {
      if (!array_key_exists('value', $informations)) {
        $informations[$redir] = true;
      } elseif (is_array($informations['value'])) {
        $informations[$redir] = true;      
      } else {
        $informations[$redir] = ($m[1] != $informations['value']);
      }
    }
  } 

  return $informations;
}

/**
 * Extract the scope from a template reference.
 *
 * A scope is defined for a data reference which value is an array. The 
 * scope provides an identifier to build template variables for array 
 * values.
 *
 * Example: {TREE.main} has MAIN for scope and MAIN.description.short
 * is a valid template variable. 
 *
 * @param (string) $reference the template reference
 * @return (string) the scope of this template reference
 */
private function fetch_getscope($reference) {

  // Extract current scope from reference
  if (preg_match('/([^\.]+)(#.*)$/', $reference, $m)
      || preg_match('/([^\.]+)$/', $reference, $m)) {
    $scope = strtoupper($m[1]);
    return $scope;
  } else {
    trigger_error("failed to extract <b>\{$reference}</b> scope", E_USER_NOTICE);
    return "";
  }
}

/*****************************************************************************/
/*                                                                           */
/*                     Documentation parsing methods                         */
/*                                                                           */
/*****************************************************************************/

/**
 * Parse the source code and its comments to build the documentation tree.
 *
 * This method supports PHP4 and PHP5 classes and function libraries.
 *
 * @return (bool) TRUE on success or FALSE on failure
 */
private function parse() {

  // For error messages
  $file = $this->filename;
  
  // Reset documentation tree structure
  $this->tree = array();

  // Require valid source code fragments
  $source_frags = preg_split('![^"^\']/\*\*\s+!s', $this->source);
  // Remove "\?\>" ending the last fragment
  $source_frags[count($source_frags) - 1] = preg_replace('!\?\>\s*$!s', "", 
                                            $source_frags[count($source_frags) - 1]);  
  // Remove banners from captured at the end of fragments
  foreach ($source_frags as $index => $fragment) {
    // Starting "/**" is NOT part of fragments at this stage, so remove "/* ... */"
    $source_frags[$index] = preg_replace('!([^"^\'])/\*.*\*/\s*$!s', "\\1", $fragment);
  }
  // Remove "}" ending the last fragment
  $source_frags[count($source_frags) - 1] = preg_replace('!\}\s*$!s', "", 
                                            $source_frags[count($source_frags) - 1]);  
  
  // Extract CVS information (if any) - this parsing is not language related
  $cvs_infos = $this->parse_cvs();  
  if (!empty($cvs_infos)) {
    $this->tree['cvs'] = $cvs_infos;  
  }

  // Only PHP language is supported yet

  if (preg_match('!(<\?php|<\?).*!s', $this->stripped_code)) {
    $this->tree['main']['language'] = "PHP";
  } else {
    $this->uparse_logerror('fatal', "source code language isn't supported (only PHP is supported), file: $file");  
    return false;
  }

  
  // Source is a library or a class ?
  $main_infos = $this->parse_main();
  if (!empty($main_infos)) {
    $this->tree['main'] = array_merge($this->tree['main'], $main_infos);  
  } else {
    // not a libray, neither a class
    $this->uparse_logerror('fatal', "no library nor class found in source code, file: $file");  
    return false;
  }
  $is_class = in_array($this->tree['main']['type'], array("class", "abstract_class", "final_class", "interface"));
  if ($is_class) {
    $class = $this->tree['main']['name'];
  }

  // Extract constants documentation (if any)
  foreach($source_frags as $source) {
    // Parse current code fragment
    $source = "/**\n" . $source;  // rebuild comment
    $constant_infos = $this->parse_constant($source);  
    if (empty($constant_infos)) continue;
    // Found a constant in the current code fragment 
    $this->tree['constant'][$constant_infos['name']] = $constant_infos['data'];  
  }

  // Extract constant groups documentation (if any)
  $constgroups = array();
  $gidx = 1;  // start group index at 1
  foreach($source_frags as $source) {
    // Parse current code fragment
    $source = "/**\n" . $source;  // rebuild comment
    $constgroup_infos = $this->parse_constant_group($source);  
    if (empty($constgroup_infos)) continue;
    // Found a constant group in the current code fragment 
    $constgroup_current = $constgroup_infos['data']; 
    if ($constgroup_infos['type'] == "const") {
      if ($is_class) {
        // Rename constants from "const" statement
        foreach ($constgroup_infos['constant'] as $const => $constdoc) {
          $constgroup_current['constant'][$class."::".$const] = $constdoc;
        }
        $constgroups[$gidx] = $constgroup_current; 
        $gidx++;
      } else {
        // Unexpected "const" statement
        foreach ($constgroup_infos['constant'] as $const => $void) {
          $this->uparse_logerror('warning', "\"const\" statement is not allowed in a library (constant \"$const\")");  
        }
      }
    } else {
      // Store constant from "define" statement
      $constgroup_current['constant'] = $constgroup_infos['constant']; 
      $constgroups[$gidx] = $constgroup_current; 
      $gidx++;
    }
  }
  if (!empty($constgroups)) {
    $this->tree['constant_group'] = $constgroups;  
  }

  // Extract properties documentation (if any)
  foreach($source_frags as $source) {
    // Parse current code fragment
    $source = "/**\n" . $source;  // rebuild comment
    $property_infos = $this->parse_property($source);  
    if (empty($property_infos)) continue;
    $this->tree['property'][$property_infos['name']] = $property_infos['data'];  
  }

  // Extract functions documentation (if any)
  foreach($source_frags as $source) {
    // Parse current code fragment
    $source = "/**\n" . $source;  // rebuild comment
    $function_infos = $this->parse_function($source);  
    if (empty($function_infos)) continue;
    // Found a function in the current code fragment 
    if ($is_class) {
      // Store function as a "method"
      $this->tree['method'][$function_infos['name']] = $function_infos['data'];  
    } else {
      $this->tree['function'][$function_infos['name']] = $function_infos['data'];  
    }
  }
  
  // Create the [constructor] and [destructor] branches 
  if ($is_class) {
    if (!empty($this->tree['method'][$class])) {
      $this->tree['constructor'] = &$this->tree['method'][$class];
    }
    if (!empty($this->tree['method']['__construct'])) {
      $this->tree['constructor'] = &$this->tree['method']['__construct'];
    }
    if (!empty($this->tree['method']['__destruct'])) {
      $this->tree['destructor'] = &$this->tree['method']['__destruct'];
    }
  }
  
  // Create the [public_method/private_method/protected_method] branch 
  if ($is_class) {
    $this->tree['public_method'] = array();
    $this->tree['private_method'] = array();
    $this->tree['protected_method'] = array();
    if (!empty($this->tree['method'])) {
      foreach ($this->tree['method'] as $method => $mdata) {
        if (!empty($mdata['info']['private'])) {
          $this->tree['private_method'][$method] = &$this->tree['method'][$method];
        } elseif (!empty($mdata['info']['protected'])) {
          $this->tree['protected_method'][$method] = &$this->tree['method'][$method];
        } else {
          $this->tree['public_method'][$method] = &$this->tree['method'][$method];
        }
      }
    }
  }

  // Create the [public_property/private_property/protected_property] branch 
  if ($is_class) {
    $this->tree['public_property'] = array();
    $this->tree['private_property'] = array();
    $this->tree['protected_property'] = array();
    if (!empty($this->tree['property'])) {
      foreach ($this->tree['property'] as $property => $pdata) {
        if (!empty($pdata['info']['private'])) {
          $this->tree['private_property'][$property] = &$this->tree['property'][$property];
        } elseif (!empty($pdata['info']['protected'])) {
          $this->tree['protected_property'][$property] = &$this->tree['property'][$property];
        } else {
          $this->tree['public_property'][$property] = &$this->tree['property'][$property];
        }
      }
    }
  }  

  // Set the number of constants declared
  if (empty($this->tree['constant'])) {
    $num_of_const = 0;
  } else {
    $num_of_const = count($this->tree['constant']);
  }
  if (!empty($this->tree['constant_group'])) {
    foreach ($this->tree['constant_group'] as $group) {
      $num_of_const +=  count($group['constant']);
    }
  }
  $this->tree['main']['constants'] = $num_of_const;

  return true; 
}

/**
 * Extract the class documentation from current file.
 *
 * This method extracts documentation informations from PHP source code and
 * comments.
 *
 * @param (string) $name the name of the class to document
 * @return (array) documentation elements extracted from code and comments:
 *                 'source' => for comment, declaration and line
 *                 'class' => for extends and implements
 *                 'description' => for short and long descriptions
 *                 'info' => for all the @info fields
 */
private function parse_class($name) {

  // Reset result
  $docu = array();

  // Load PHP source
  $php_source = $this->source;

  // Extract class comment and declaration
  if (preg_match('!.*(/\*\*\s.*\*/)\s*\n\s*(//.*\n\s*)*'.
                 '((class|interface|abstract\s+class|final\s+class)\s+'.
                 $name.'.*?)\{!s',
                 $php_source, $m)) {
    $docu['source']['comment'] = trim($m[1]);  
    $docu['source']['declaration'] = trim($m[3]);  
  } else {
    $this->uparse_logerror('warning', "failed to extract class declaration and comment (class \"$name\")");
    return array();  // can't do more
  }

  // Extract class code starting line number
  $start = strpos($this->source, $docu['source']['declaration']);
  $docu['source']['line'] = substr_count(substr($this->source, 0, $start), "\n") + 1;

  // Extract parent and interfaces from class declaration
  if (preg_match('!extends\s+(.*?)(\s|$)!s', $docu['source']['declaration'], $m)) {
    $docu['source']['extends'] = trim($m[1]);  
  }
  if (preg_match('!implements\s+(.*?)(extends|$)!s', $docu['source']['declaration'], $m)) {
    $interfaces = preg_replace('!\s!', "", $m[1]);  
    $docu['source']['implements'] = explode(",", $interfaces);
  }

  // Parse the comment into description and @info elements
  $comment_elements = $this->uparse_comment($docu['source']['comment']);
  if (!empty($comment_elements['description'])) {
    $docu['description'] = $comment_elements['description'];
  }
  if (!empty($comment_elements['info'])) {
    $docu['info'] = $comment_elements['info'];
  }

  return $docu;
}

/**
 * Extract documentation of a constant from a source code fragment.
 *
 * This method extracts documentation informations from a PHP source code and
 * fragment, which is a documentation block comment followed by the related
 * source code.
 *
 * @param (string) $source a source code fragment to parse
 * @return (array) an empty array if there's no constant in the code fragment,
 *                 an associative array with documentation elements:
 *                 'name' => the constant identifier
 *                 'data' => array of documentation elements:
 *                           'source' => for comment, declaration and line
 *                           'description' => for short and long descriptions
 *                           'info' => for all the @info fields 
 *                 'type' => the defination type ("const" or "define")
 */
private function parse_constant($source) {

  // Reset result
  $docu = array();

  // Extract constant comment and code from PHP5 "const" syntax
  if (preg_match('!(/\*\*.*\*/)\s*\n\s*(//.*\n\s*)*'.
                 '(const\s+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*'.
                 '=\s*(.*?);)\s*(//(.*?)\n)*!s', 
                 $source, $m)
      // Avoid constant grouped at least by 2
      && !preg_match('!\n\s*const\s.*;.*\n\s*const\s.*;!s', 
                     $source)) {
    $docu['type'] = "const";
    $docu['name'] = $this->tree['main']['name'] . "::" . trim($m[4]);
    $docu['data']['source']['comment'] = trim($m[1]);  // the comment for the constant block
    $docu['data']['source']['code'] = trim($m[3]);
    $docu['data']['source']['value'] = trim($m[5]);
  // Extract constant comment and code from "define" syntax
  } elseif (preg_match('!(/\*\*.*\*/)\s*\n\s*(//.*\n\s*)*'.
                       '(define\s*\(\s*["\']([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)["\']\s*,'.
                       '\s*(.*?)\s*\)\s*;)\s*(//(.*?)\n)*!s', 
                       $source, $m)
      // Avoid constant grouped at least by 2
      && !preg_match('!\n\s*define(\s|\().*;.*\n\s*define(\s|\().*;!s', 
                     $source)) {
    $docu['type'] = "define";
    $docu['name'] = trim($m[4]);
    $docu['data']['source']['comment'] = trim($m[1]);  // the comment for the constant block
    $docu['data']['source']['code'] = trim($m[3]);
    $docu['data']['source']['value'] = trim($m[5]);
  // No constant found in this code fragment
  } else {
    return array();
  }

  // Extract constant code starting line number
  $start = strpos($this->source, $docu['data']['source']['code']);
  $docu['data']['source']['line'] = substr_count(substr($this->source, 0, $start), "\n") + 1;

  // Constant name for error messages
  $name = $docu['name'];
  
  // Parse the comment into description and @info elements
  $comment_elements = $this->uparse_comment($docu['data']['source']['comment']);
  if (!empty($comment_elements['description'])) {
    $docu['data']['description'] = $comment_elements['description'];
  }
  if (!empty($comment_elements['info'])) {
    $docu['data']['info'] = $comment_elements['info'];
  }
  
  return $docu;
}

/**
 * Extract documentation of constants declared as a group.
 *
 * This method extracts documentation informations from a PHP source code and
 * fragment, which is a documentation block comment followed by the related
 * source code.
 *
 * @param (string) $source a source code fragment to parse
 * @return (array) an empty array if there's no constant group in the code fragment,
 *                 an associative array with documentation elements:
 *                 'data' => array of documentation elements:
 *                           'source' => for comment, declaration and line
 *                           'description' => for short and long descriptions
 *                           'info' => for all the @info fields 
 *                 'type' => the defination type ("const" or "define")
 *                 'constant' => array of constants in the group
 */
private function parse_constant_group($source) {

  // Reset result
  $docu = array();

  // Extract constant group comment and code from PHP5 "const" syntax
  if (preg_match('!(/\*\*.*\*/)(.*?)'.
                 '(\n\s*const\s.*;.*\n\s*const\s.*;.*)$!s', 
                 $source, $m)) {
    $docu['type'] = "const";
    $docu['data']['source']['comment'] = trim($m[1]);  // the comment for the constant group
    $docu['data']['source']['code'] = trim($m[3]);
    $code = $m[3];
    // Extract constant group code starting line number
    $start = strpos($this->source, $docu['data']['source']['code']);
    $docu['data']['source']['line'] = substr_count(substr($this->source, 0, $start), "\n") + 1;
    $line = $docu['data']['source']['line'];
    // Parse the group comment into documentation elements
    $comment_elements = $this->uparse_comment($docu['data']['source']['comment']);
    if (!empty($comment_elements['description'])) {
      $docu['data']['description'] = $comment_elements['description'];
    }
    if (!empty($comment_elements['info'])) {
      $docu['data']['info'] = $comment_elements['info'];
    }
    // Parse every constant in the current group
    if (preg_match_all('!(const\s+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*'.
                       '=\s*(.*?);)[\x20\t]*(//(.*?)\n)*!s',
                       $code, $consts, PREG_PATTERN_ORDER)) {
      // Documenting every constant in the group
      for ($i = 0; $i < count($consts[0]); $i++) {
        // Constant number for error messages
        $const_num = $i + 1;
        // Constant source code
        if (!empty($consts[0][$i])) {
          $const_source = $consts[0][$i];
        } else {
          $this->uparse_logerror('warning', "failed to extract constant $const_num source code from line #$line constant group");
          continue;
        }
        if (!empty($consts[2][$i])) {
          $const_name = $consts[2][$i];
        } else {
          $this->uparse_logerror('warning', "failed to extract constant $const_num name from line #$line constant group");
          continue;
        }
        // Extract constant code starting line number
        $const_start = strpos($this->source, $const_source);
        $const_line = substr_count(substr($this->source, 0, $const_start), "\n") + 1;
        $const_value = $consts[3][$i];
        $docu['constant'][$const_name]['line'] = $const_line;
        $docu['constant'][$const_name]['comment'] = trim(preg_replace('!\s*//\s*!', "", $consts[4][$i]));
        $docu['constant'][$const_name]['code'] = trim($consts[1][$i]);
        $docu['constant'][$const_name]['value'] = trim($consts[3][$i]);
      }
    }
  // Extract constant group comment and code from "define" syntax
  } elseif (preg_match('!(/\*\*.*\*/)(.*?)'.
                       '(\n\s*define(\s|\().*;.*\n\s*define(\s|\().*;.*)$!s', 
                       $source, $m)) {
    $docu['type'] = "define";
    $docu['data']['source']['comment'] = trim($m[1]);  // the comment for the constant group
    $docu['data']['source']['code'] = trim($m[3]);
    $code = $m[3];
    // Extract constant group code starting line number
    $start = strpos($this->source, $docu['data']['source']['code']);
    $docu['data']['source']['line'] = substr_count(substr($this->source, 0, $start), "\n") + 1;
    $line = $docu['data']['source']['line'];
    // Parse the group comment into documentation elements
    $comment_elements = $this->uparse_comment($docu['data']['source']['comment']);
    if (!empty($comment_elements['description'])) {
      $docu['data']['description'] = $comment_elements['description'];
    }
    if (!empty($comment_elements['info'])) {
      $docu['data']['info'] = $comment_elements['info'];
    }
    // Parse every constant in the current group
   if (preg_match_all('!(define\s*\(\s*["\']([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)["\']\s*,'.
                       '\s*(.*?)\s*\)\s*;)[\x20\t]*(//(.*?)\n)*!s', 
                       $code, $consts, PREG_PATTERN_ORDER)) {
      // Documenting every constant in the group
      for ($i = 0; $i < count($consts[0]); $i++) {
        // Constant number for error messages
        $const_num = $i + 1;
        // Constant source code
        if (!empty($consts[0][$i])) {
          $const_source = $consts[0][$i];
        } else {
          $this->uparse_logerror('warning', "failed to extract constant $const_num source code from line #$line constant group");
          continue;
        }
        if (!empty($consts[2][$i])) {
          $const_name = $consts[2][$i];
        } else {
          $this->uparse_logerror('warning', "failed to extract constant $const_num name from line #$line constant group");
          continue;
        }
        // Extract constant code starting line number
        $const_start = strpos($this->source, $const_source);
        $const_line = substr_count(substr($this->source, 0, $const_start), "\n") + 1;
        $const_value = $consts[3][$i];
        $docu['constant'][$const_name]['line'] = $const_line;
        $docu['constant'][$const_name]['comment'] = trim(preg_replace('!\s*//\s*!', "", $consts[4][$i]));
        $docu['constant'][$const_name]['code'] = trim($consts[1][$i]);
        $docu['constant'][$const_name]['value'] = trim($consts[3][$i]);
      }
    }
  } else {
    return array();
  }
   
  return $docu;
}

/**
 * Extract CVS information from current file.
 *
 * CVS informations are expressions like "$Keyword: the CVS keyword value $"
 * except the $\Log$ which is spread on several lines. You may notice that
 * a $\ is used instead of $ in these comments and a \s{0} is used in this
 * PHP code as well to avoid confusion with real CVS information of this file.
 *
 * Supported CVS keywords are "Author", "CVSHeader", "Date", "Header", "Id",
 * "Name", "Locker", "Log", "RCSfile", "Revision", "Source", "State".
 *
 * All, but $\Log$,  are of simple format like:
 *     $\Author: jmfaure $
 *     $\Date: 2005/10/07 13:44:48 $
 *
 * The format for the $\Log$ keyword is a bit more complex:
 *     $\Log: Mdoc.php,v $
 *     Revision 1.5  2005/10/07 13:44:48  jmfaure
 *     Test CVS Author and Date keywords in comment lines starting with a *
 *
 *     Revision 1.4  2005/10/07 13:43:27  jmfaure
 *     Test all CVS keywords
 *
 *     Revision 1.3  2005/10/07 13:41:35  jmfaure
 *     Test some CVS keywords
 *
 * @return (array) CVS informations found in source code
 */
private function parse_cvs() {

  // Reset result
  $cvs = array();
  
  // Create the working string to contain all comments
  $work = "";
  
  // Extract all block comments
  if (preg_match_all('!(/\*.*?\*/)!s', $this->source, $m)) {
    $work .= implode("\n", $m[0]);
  }
  
  // Extract full-line comments
  if (preg_match_all('!^\s*(//.*)$!m', $this->source, $m)) {
    $work .= implode("\n", $m[0]);
  }
  if (preg_match_all('!^\s*(#.*)$!m', $this->source, $m)) {
    $work .= implode("\n", $m[0]);
  }

  // Extract single-line CVS keywords (first occurence)
  $singleline_keys = array("Author", "CVSHeader", "Date", "Header", 
                           "Id", "Name", "Locker", "RCSfile", 
                           "Revision", "Source", "State");
  foreach ($singleline_keys as $keyword) {
    if(preg_match('!^.*\$'.$keyword.':\s*(.*?)\s*\$!m', $work, $m)) {
      $cvs[$keyword] = $m[1];
    }
  }
  
  // Extract the $\Log$ information
  if(preg_match('!\n([^\n]*)\$\s{0}Log:\s*(.*)\$\s*' .
                '(\n\1Revision[^\n]*\n.*)\n\1\n\1[^R][^e][^v][^i][^s][^i][^o][^n]!s', 
                $work, $m)) {
    $prefix = $m[1];
    $cvs["Log"] = $m[2] . str_replace("\n$prefix", "\n", $m[3]);
  // Need a regex for cases where the $Log\$ is not followed by its prefix (blank line for example)
  } elseif(preg_match('!\n([^\n]*)\$\s{0}Log:\s*(.*)\$\s*' .
                '(\n\1Revision[^\n]*\n.*)\n\1\n!s', 
                $work, $m)) {
    $prefix = $m[1];
    // m[3] must be truncated at \n followed by not \1
    if (preg_match('!(('.$prefix.'[^\n]*\n)+)!s', $m[3], $n)) {
      $detail = "\n" . $n[1];
    } else {
      $detail = $m[3];  // failed to clean so keep it ram
    }
    $cvs["Log"] = $m[2] . rtrim(str_replace("\n$prefix", "\n", $detail));
  }

  return $cvs;
}

/**
 * Extract documentation of a function or method from a source code fragment.
 *
 * This method extracts documentation informations from a PHP source code and
 * fragment, which is a documentation block comment followed by the related
 * source code.
 *
 * @param (string) $source a source code fragment to parse
 * @return (array) an empty array if there's no function in the code fragment,
 *                 an associative array with documentation elements:
 *                 'name' => the function identifier
 *                 'data' => array of documentation elements:
 *                           'source' => for comment, declaration and line
 *                           'description' => for short and long descriptions
 *                           'info' => for all the @info fields
 *                           'param' => for parameters
 *                           'return' => for return values
 */
private function parse_function($source) {

  // Reset result
  $docu = array();

  // Extract method comment and code (PHP4/PHP5 syntax)
  if (preg_match('!(/\*\*.*\*/)\s*\n\s*(//.*\n\s*)*'.
                 '(((abstract|public|private|protected|static|final)\s+)*'.
                 'function\s+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\s\(].*)!s', 
                 $source, $m)) {
    $docu['name'] = trim($m[6]);
    $docu['data']['source']['comment'] = trim($m[1]);  // the comment for the constant block
    $docu['data']['source']['code'] = trim($m[3]);
  // No function found in this code fragment
  } else {
    return array();
  }
  // Extract constant code starting line number
  $start = strpos($this->source, $docu['data']['source']['code']);
  $docu['data']['source']['line'] = substr_count(substr($this->source, 0, $start), "\n") + 1;

  // Functor
  $name = $docu['name'];
    
  // Extract method types from code and set as @info (PHP4/PHP5 compliance)
  list($words, $void) = split("function", $docu['data']['source']['code'], 2);
  if (preg_match_all('!([a-zA-Z]+)!', $words, $m)) {
    $docu['data']['info'] = array();
    foreach($m[1] as $w) { 
      $docu['data']['info'][$w] = "yes";
    }  
  }
  
  // Parse the comment into documentation elements
  $comment_elements = $this->uparse_comment($docu['data']['source']['comment']);
  if (!empty($comment_elements['description'])) {
    $docu['data']['description'] = $comment_elements['description'];
  }
  if (!empty($comment_elements['info'])) {
    // Split the param list comments and move to [param] branch
    if (!empty($comment_elements['info']['param'])) {
      $param_list = is_array($comment_elements['info']['param']) ?
                    $comment_elements['info']['param'] :         // case of multiple parameters
                    array($comment_elements['info']['param']);  // case of single parameter
      $docu['data']['param'] = $this->uparse_splitparams($param_list, $docu['data']['source']['code']);
      unset($comment_elements['info']['param']);
    }
    // Split return comment in multiple values and move to [return] branch
    if (!empty($comment_elements['info']['return'])) {
      $docu['data']['return'] = $this->uparse_splitreturn($comment_elements['info']['return'], $name);
      unset($comment_elements['info']['return']);
    }
    // Add remaining @infos in [info] branch
    if (!empty($comment_elements['info'])) {
      if (!empty($docu['data']['info'])) {
        $docu['data']['info'] = array_merge($docu['data']['info'], $comment_elements['info']);
      } else {
        $docu['data']['info'] = $comment_elements['info'];
      } 
    } 
  }

  // Complete the documentation with method/function extensions
  // FUNCTION.source.return_type or METHOD.source.return_type
  if (empty($docu['data']['return'])) {
    $docu['data']['source']['return_type'] = "void";
  } elseif (count($docu['data']['return']) == 1) {
    $docu['data']['source']['return_type'] = $docu['data']['return'][0]['type'];
  } else {
    $docu['data']['source']['return_type'] = "mixed";
  }
  // FUNCTION.source.parameters or METHOD.source.parameters
  if (empty($docu['data']['param'])) {
    $paramstr = "void";
  } else {
    $paramstr = "";  // reset parameter string
    $nbparams = count($docu['data']['param']);
    $index = 0;  // follow on which parameter is processed in the each loop 
    $openbrack = 0;  // count brackets opened
    foreach ($docu['data']['param'] as $p => $pdef) {
      // Open [ for optional param
      if (!empty($pdef['default'])) {
        $paramstr .= " [";
        ++$openbrack;
      }
      // Only first param don't start with a comma
      ++$index;
      $paramstr .= ($index == 1) ? $pdef['type'] . " $p" :
                                   ", " . $pdef['type'] . " $p";      
    }
    // Close open brackets
    $paramstr .= str_repeat("]", $openbrack);
  }
  $docu['data']['source']['parameters'] = $paramstr;
  // METHOD.source.member
  if (in_array($this->tree['main']['type'], array("class", "abstract_class", "final_class", "interface"))) {
    if (!empty($docu['data']['info']['protected'])) {
      $member = "protected";
    } elseif (!empty($docu['data']['info']['private'])) {
      $member = "private";
    } else {
      $member = "public";
    }
    if (!empty($docu['data']['info']['abstract'])) {
      $member .= " abstract";
    }
    if (!empty($docu['data']['info']['final'])) {
      $member .= " final";
    }
    if (!empty($docu['data']['info']['static'])) {
      $member .= " static";
    }
    $docu['data']['source']['member'] = $member;
  }

  return $docu;
}

/**
 * Extract the library documentation from current file.
 *
 * This method extracts documentation informations from PHP source code and
 * comments.
 *
 * @return (array) documentation elements extracted from code and comments:
 *                 'source' => for comment, declaration and line
 *                 'description' => for short and long descriptions
 *                 'info' => for all the @info fields
 */
private function parse_library() {

  // Reset result
  $docu = array();

  // Load PHP source
  $php_source = $this->source;

  // Extract library comment
  if (preg_match('!(/\*\*\s.*?\n\s*\*\s*@lib\s+(\w*).*?\*/)!s',
                 $php_source, $m)) {
    $docu['source']['comment'] = trim($m[1]);  
    $name = trim($m[2]);  // for error messages
  } else {
    $this->uparse_logerror('warning', "failed to extract library comment");
    return array();  // can't do more
  }

  // Parse the comment into description and @info elements
  $comment_elements = $this->uparse_comment($docu['source']['comment']);
  if (!empty($comment_elements['description'])) {
    $docu['description'] = $comment_elements['description'];
  }
  if (!empty($comment_elements['info'])) {
    $docu['info'] = $comment_elements['info'];
  }

  // Complete the documentation with library extensions
  // TREE.main.name
  $docu['name'] = $docu['info']['lib'];
  
  return $docu;
}

/**
 * Extract documentation of the class or library found in current file.
 *
 * This method finds the main documentation comment, so is class or libray, 
 * and then parses this comment into documentation elements. 
 *
 * @return (array) class or library informations found in source code
 */
private function parse_main() {

  // Reset result
  $main = array();
  
  // Require commented source (as of first doc comment)
  list($void, $php_source) = preg_split('!/\*\*\s+!s', $this->source, 2);
  $php_source = "/**\n" . $php_source;  // rebuild a correct comment

  // Try complete class/interface structure extraction
  if (preg_match('!\n\s*(class|interface|abstract\s+class|final\s+class)\s+(.*?)[\s\{]!s',
                 $php_source, $m)) {
    // Class statement found
    // -- type is 'class', 'interface', 'abstract_class' or 'final_class'
    $main['type'] = preg_replace('!\s+!s', "_", $m[1]);  // remove blanks in compound types
    $main['name'] = $m[2];  
    $main = array_merge($main, $this->parse_class($main['name']));
  } elseif (preg_match('!(/\*\*\s.*?\n\s*\*\s*@lib\s+(\w*).*?\*/)!s',
                       $php_source, $m)) {
    // Not a class, so is a function library
    $main['type'] = 'library';
    $main = array_merge($main, $this->parse_library());
  } else {
    // Not a class nor library
    $this->uparse_logerror('warning', "no class, no library found in this file");  
    return $main;
  }

  return $main;
}

/**
 * Extract documentation of a property from a source code fragment.
 *
 * This method extracts documentation informations from a PHP source code and
 * fragment, which is a documentation block comment followed by the related
 * source code.
 *
 * @param (string) $source a source code fragment to parse
 * @return (array) an empty array if there's no property in the code fragment,
 *                 an associative array with documentation elements:
 *                 'name' => the property identifier
 *                 'data' => array of documentation elements:
 *                           'source' => for comment, declaration and line
 *                           'description' => for short and long descriptions
 *                           'info' => for all the @info fields 
 */
private function parse_property($source) {

  // Reset result
  $docu = array();

  // Extract property comment and code from PHP5 syntax
  if (preg_match('!(/\*\*.*\*/)\s*\n\s*(//.*\n\s*)*'.
                 '((static\s+)*(public|private|protected)\s+(static\s+)*'.
                 '\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*'.
                 '=*\s*(.*);)\s*(//(.*?)\n)*!s', 
                 $source, $m)) {
    $found_php = "PHP5";  // to select appropriate unallowed comments
    $docu['name'] = trim($m[7]);
    $docu['data']['source']['comment'] = trim($m[1]);  // the comment for the constant block
    $docu['data']['source']['code'] = trim($m[3]);
    $docu['data']['info'][$m[5]] = "yes";  // public, private or protected 
    if (!empty($m[4]) || !empty($m[6])) {
      $docu['data']['info']['static'] = "yes";
    }
    if (!empty($m[8])) {
      $docu['data']['source']['initial'] = trim($m[8]);
    }
  // Extract property comment and code from PHP4 syntax
  } elseif (preg_match('!(/\*\*.*\*/)\s*\n\s*(//.*\n\s*)*'.
                 '(var\s+\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*'.
                 '=*\s*(.*);)*\s*(//(.*?)\n)*!s', 
                 $source, $m)
                 && !empty($m[4])) {
    $found_php = "PHP4";  // to select appropriate unallowed comments
    $docu['name'] = trim($m[4]);
    $docu['data']['source']['comment'] = trim($m[1]);  // the comment for the constant block
    $docu['data']['source']['code'] = trim($m[3]);
    if (!empty($m[5])) {
      $docu['data']['source']['initial'] = trim($m[5]);
    }
  // No property found in this code fragment
  } else {
    return array();
  }

  // Extract property code starting line number
  $start = strpos($this->source, $docu['data']['source']['code']);
  $docu['data']['source']['line'] = substr_count(substr($this->source, 0, $start), "\n") + 1;

  // Property name for error messages
  $name = $docu['name'];

  // Extract property type from code and set as @info (PHP4/PHP5 compliance)
  if (preg_match('!(public|private|protected)\s+\$.*!', $docu['data']['source']['code'], $m)) {
    $docu['data']['info'] = array();
    $docu['data']['info'][$m[1]] = "yes";
  }
  
  // Parse the comment into description and @info elements
  $comment_elements = $this->uparse_comment($docu['data']['source']['comment']);
  if (!empty($comment_elements['description'])) {
    $docu['data']['description'] = $comment_elements['description'];
  }
  if (!empty($comment_elements['info'])) {
    if (!empty($docu['data']['info'])) {
      $docu['data']['info'] = array_merge($docu['data']['info'], $comment_elements['info']);
    } else {
      $docu['data']['info'] = $comment_elements['info'];
    } 
  } 
  
  // Force @type empty value if @type is missing
  if (!array_key_exists('type', $docu['data']['info'])) {
    $docu['data']['info']['type'] = "";
  }

  // Complete the documentation with property extensions
  // PROPERTY.source.member
  if (!empty($docu['data']['info']['protected'])) {
    $member = "protected";
  } elseif (!empty($docu['data']['info']['private'])) {
    $member = "private";
  } else {
    $member = "public";
  }
  if (!empty($docu['data']['info']['final'])) {
    $member .= " final";
  }
  if (!empty($docu['data']['info']['static'])) {
    $member .= " static";
  }
  $docu['data']['source']['member'] = $member;
       
  return $docu;
}

/*****************************************************************************/
/*                                                                           */
/*                        Common parsing utilities                           */
/*                                                                           */
/*****************************************************************************/

/**
 * Utility to parse a structured source code comment.
 *
 * This method extracts documentation elements from a structured comment
 * (describing class, method, property, constant, ...).
 *
 * A structured source code comment starts with "/** " followed by a
 * one-line short description, then an optional long description ending
 * by a blank line followed by the optional @info lines.
 * 
 * Text formating is kept (\n\s\t) so a display method will be able
 * to output a smart HTML code similar to plain text representation.
 *
 * [example]
 * For a such a comment:
 *
 *                        / **
 *                          * Short description
 *                          *
 *                          * Long description 
 *                          * is spread onto
 *                          * three lines.
 *                          *
 *                          * @experimental
 *                          * @warning unstable
 *                          * @param (string) $p1 the first parameter
 *                          * @param (int) $p2 the second parameter
 *                          * @return (void) if echo flag is TRUE
 *                                           a line of comment to test parsing
 *                                    (string) the solution if echo flag is FALSE (default)
 *                          * /
 *
 * The parsing gives an array like:
 *          array (
 *            'description' => 
 *                      array (
 *                        'short' => 'Short description',
 *                        'long' => 'Long description 
 *                                  is spread onto
 *                                  three lines.',
 *                            ),
 *            'info' => 
 *                      array (
 *                        'experimental' => 'yes',
 *                        'warning' => 'unstable',
 *                        'param' => 
 *                                array (
 *                                  0 => '(int) $p1 the first parameter',
 *                                  1 => '(int) $p2 the second parameter,
 *                                ),
 *                        'return' => '(void) if echo flag is TRUE
 *                                    a line of comment to test parsing
 *                                    (string) the solution if echo flag is FALSE (default)',
 *                            ),
 *          )
 *
 * @param (string) $comment source code comment to parse
 * @return (array) manual information from comment:
 *                 'description' => array with short and long description
 *                 'info' => array with all @info fields
 */
private function uparse_comment($comment) {

  // Reset result
  $elements = array();

  // Need cleaned comment to extract descriptions and @info
  $work = preg_replace('!^/\*\*\s\s*!s', "", trim($comment));  // remove "/**" at start
  $work = preg_replace('!\*/$!s', "", $work);                  // remove "*/" at end
  $work = preg_replace('!^\s*\*(\x20|$)!m', "", $work);        // remove "*" and blanks in every line beginning

  // Split comment in items (getting descriptions first)
  $items = explode("\n@", $work);
  
  // Prevent this case which should not happen
  if (count($items) == 0) {
    return array();
  }
  
  // Ensure first item is description so doesn't start with a @
  if (substr($items[0], 0, 1) != '@') {
    // Split and get descriptions
    $descriptions = preg_split('!\n!s', $items[0], 2);
    $short = !empty($descriptions[0]) && strlen(trim($descriptions[0])) > 0 ?
             trim($descriptions[0]) :
             "";
    $long = !empty($descriptions[1]) && strlen(trim($descriptions[1])) > 0 ?
            trim($descriptions[1]):
            "";
    if (!empty($short)) $elements['description']['short'] = $short;
    if (!empty($long)) $elements['description']['long'] = $long;
    array_shift($items);  // descriptions item done so is removed
  } else {
    // The starting @ must be removed if descriptions are missing
    $items[0] = ltrim($items[0], '@');
  }
  
  // Are we done, no @info ?
  if (count($items) == 0) {
    return $elements;
  }

  // Browse items and parse them
  $infos = array();
  foreach ($items as $it) {
    // Get one-field comments
    if (preg_match('!^([^\s]+)\s*$!s', $it, $m)) {
      $infos[$m[1]] = "yes";  // for example $infos['private'] => "yes"
    // Get two-field comments 
    } elseif (preg_match('!^([^\s]+)\s+(.*)$!s', $it, $m)) {
      // Set first and second field values (better reading)
      $first = $m[1];
      $second = trim($m[2]);
      // Stack multiple @param entries
      if (array_key_exists($first, $infos)) {
        if ($first == "param") {
          if (is_array($infos[$first])) {
            array_push($infos[$first], $second);
          } else {
            $infos[$first] = array($infos[$first], 
                                   $second);
          }
        } else {
          $this->uparse_logerror('notice', "info @$first has multiple values in comment of \"$short\"");
        }
      } else {
        // Set a single two-field entry
        $infos[$first] = $second;  // for example $infos['warning'] => "unstable"
      }
    } else {
      $this->uparse_logerror('notice', "failed to parse the item \"$it\" in comment of \"$short\"");
    }
  }
  // Add @infos to comment elements
  if (!empty($infos)) {
    $elements['info'] = $infos;
  }

  return $elements;
}

/**
 * Utility to return or output the parser log.
 *
 * By default, the internal parser log is returned in an array. Otherwise, 
 * setting the echo flag to TRUE, the internal parser log is printed out
 * in human-readable format.
 *
 * @param (bool) $echo set this flag TRUE to echo the parser log
 * @return (array) the internal parser log array (default)
 *         (void) when the internal parser log is printed out
 */
public function uparse_exportlog($echo = false) {

  // Print  or return tree
  if ($echo) {
    print_r($this->parser_log);  // effortless human-readable format
    return;
  } else {
    return $this->parser_log;   // thus can be processed in a script
  }
}

/**
 * Utility to log a parsing error.
 *
 * The error is simply append as an array ('level', 'message') in the log
 * array property. Errors can be logged at one of the three levels: notice,
 * warning or fatal. There's no control on the level nor message entries.
 *
 * @param (string) $level error level is notice, warning or fatal
 * @param (string) $message a human readbale error message
 * @return (void)
 */
private function uparse_logerror($level, $message) {

  // Append error components as an array
  $this->parser_log[] = array('level' => $level,
                              'message' => $message,
                        );
}

/**
 * Utility to parse a @param comment field.
 *
 * This method is used to extract documentation informations for PHP source code.
 *
 * @param (string) $comment source code comment to parse
 * @return (array) documentation information from comment:
 *                 'name' => parameter name, without starting $
 *                 'type' => parameter type
 *                 'description' => parameter description (can be multiline)
 */
private function uparse_paraminfo($comment) {

  // Syntax is @param (type) $pname the param description (can be multiline)
  if (preg_match('!^\((.*?)\)\s+\$(.*?)\s+(.*?)$!s', $comment, $m)) {
    // Return manual information
    return array('name' => trim($m[2]),
                 'type' => trim($m[1]),
                 'description' => trim($m[3]));
  } else {
    $paramstr = substr(str_replace("\n", " ", $comment), 0, 40) . " ...";  // remove \n to add in log and cut reasonably
    $this->uparse_logerror('warning', "failed to parse a @param item from comment \"$paramstr\"");
    return array();
  }
}

/**
 * Utility to remove comments from PHP source code.
 *
 * Remove comment lines of any style (/* , //, #) and the PHP xml tag (<?).
 *
 * @param (string) $source the source code to clean up
 * @return (string) the code without comments
 */
private function uparse_removecomments($source) {

  // Initiate working string
  $clean = $source;
  
  // Remove C-style block comments
  $clean = preg_replace('!/\*.*?\*/!s', "", $clean);

  // Remove blank lines to have contiguous comment lines
  $clean = preg_replace('!\s*\n+!', "\n", $clean);
 
  // Remove comments outside instruction blocks 
  // -- from block end "; or }" to next non-comment block
  $clean = preg_replace('!(^|;|\})\s*\n\s*(//|#).*?\n\s*([^/])!s', 
                        "\\1\n\\3", $clean);  
  $clean = trim($clean);
    
  return $clean;
}

/**
 * Utility to split the parameter list in individual parameter documentations.
 *
 * The parameter list is extracted in raw format: 
 *
 *     array (
 *       [0] => (int) &$p1 the first parameter
 *       [1] => (mixed) $p2 the title of text (string)
 *                      or FALSE (bool) to get all
 *       [2] => (string) $p3 the third parameter
 *           )
 *
 * This method parses each parameter comment into an array of arrays:
 *
 *     array (
 *       [p1] => array (
 *                [type] => int
 *                [default] => 
 *                [description] => the first parameter
 *                [reference] => yes
 *                    )
 *       [p2] => array (
 *                [type] => mixed
 *                [default] => 
 *                [description] => the title of text (string)
 *                                 or FALSE (bool) to get all
 *                [reference] =>
 *                    )
 *       [p3] => array (
 *                [type] => string
 *                [default] => "a string"
 *                [description] => the third parameter
 *                [reference] =>
 *                    )
 *           )
 *
 * The four array keys (type, default, description and reference) are always
 * defined, eventualy empty.
 *
 * @param (array) $parameters the parameter list
 * @param (string) $code the function (method) code
 * @return (array) the list of indivual parameter documentation
 */
private function uparse_splitparams($parameters, $code) {
 
  // Reset result
  $params = array();

    // Extract parameters from code
  if (preg_match('!^.*?function\s*(.*?)\s*\(([^\n]*)\).*?\n!s', $code, $m)) {
    // Call can be empty if parameters are implicit and computed with func_get_arg() and family
    if (!empty($m[1])) {
      // Process every parameter
      $functor = $m[1];
      $pstring = $m[2];
      $p_strings = explode(",", $pstring);
      foreach ($p_strings as $p) {
        // Get param name
        if (!preg_match('!\$([^\s^=]+)!', $p, $m)) {
          $this->uparse_logerror('warning', "can't extract parameter name from code $p in $functor");
          continue;
        }
        $pname = $m[1];
        $params[$pname] = array();
        // Get param type (optional)
        if (preg_match('!([^\s^&]+)[\s\&]+\$[^\s]+!', $p, $m)) {
          $params[$pname]['type'] = $m[1];
        } else {
          $params[$pname]['type'] = "";
        }
        // Get param default value (optional)
        if (preg_match('!\$[^\s]+\s*=\s*(.*)!', $p, $m)) {
          $params[$pname]['default'] = $m[1];
        } else {
          $params[$pname]['default'] = "";
        }
        // Get if param is passed by reference
        if (preg_match('!\&\s*\$'.$pname.'!', $p)) {
          $params[$pname]['reference'] = "yes";
        } else {
          $params[$pname]['reference'] = "";
        }
      }
    }
  }

  // Complete param documentation with related @params comments
  $plist = array();  // reset list to track parameters without related comment
  // Ensure there is at least one param comment
  if (!empty($parameters)) {
    foreach($parameters as $pcomm) {
      // Parse @param comment
      $pdata = $this->uparse_paraminfo($pcomm);
      if (empty($pdata)) {
        $this->uparse_logerror('warning', "failed to parse @param comment \"$pcomm\" in $functor");
        continue;  // parsing error is already logged by parse_param() method
      }
      // Better code reading
      $p_name = $pdata['name'];  
      $p_type = $pdata['type'];  
      $p_desc = $pdata['description'];  
      // Store in tracking list
      array_push($plist, $p_name);
      // Are declared type and comment compliant ?
      if (!empty($params[$p_name]['type'])
          && $params[$p_name]['type'] != $p_type) {
        $this->uparse_logerror('warning', "incorrect @param '$p_name' type in comment \"$pcomm\" in $functor");
      }
      if (empty($params[$p_name]['type'])) {
        // Set type if not done in code
        $params[$p_name]['type'] = $p_type;
      }
      // Set description
      $params[$p_name]['description'] = $p_desc;
    }
  }
  
  // Check if any missing param comment
  $p_incode = array_keys($params);
  foreach ($p_incode as $p) {
    if (!in_array($p, $plist)) {
      $this->uparse_logerror('warning', "didn't find comment for @param '$p' in $functor");
    }
  }

  return $params;
} 

/**
 * Utility to split a return comment into multiple values.
 *
 * The return comment is unique but it frequently represents multiple
 * alternatives: 
 *
 *     'return' => '(void) if echo flag is TRUE
 *                         a line of comment to test parsing
 *                  (string) the solution if echo flag is FALSE (default)',
 *
 * This method splits a return comment into multiple values commented
 * into an array of arrays:
 *
 *     array (
 *       0 => 
 *            array (
 *              'type' => void,
 *              'description' => 'if echo flag is TRUE,
 *                                a line of comment to test parsing',
 *                  ),
 *       1 =>
 *            array (
 *              'type' => string,
 *              'description' => the solution if echo flag is FALSE (default)',
 *                  ),
 *     ),
 *
 * @param (string) $comment the return comment to split
 * @param (string) $functor the related function name (for error message)
 * @return (array) the list of return values
 */
private function uparse_splitreturn($comment, $functor) {
 
  // Reset result
  $values = array();
  
  // Syntax is @return (type1) description for return case 1 (can be multiline)
  //                   (type2) description for the other case
  $ret = preg_replace('!\n\s*\(!', "\n((", $comment);  // add a second "(" which will be removed by explode()
  $ret_alternatives = explode("\n(", $ret);
  foreach ($ret_alternatives as $r) {
    if (preg_match('!\((.*?)\)\s*(.*)!s', $r, $m)) {
      // Return manual information
      $values[] = array('type' => trim($m[1]),
                        'description' => trim($m[2]));
    } else {
      $returnstr = substr(str_replace("\n", " ", $comment), 0, 40) . " ...";  // remove \n to add in log and cut reasonably
      $this->uparse_logerror('warning', "failed to parse @return alternative \"$returnstr\" in $functor");
    }
  }

  return $values;
} 
}
/***************************************************************************

       $Log: Mdoc.php,v $
       Revision 1.1  2006/02/04 15:23:38  jmfaure
       Import Mdoc and Mtest classes in ./include/php5 folder.

 **************************************************************************/
?>